Merge pull request #848 from thecodingmachine/develop
Deploy 2021-03-29
@ -1,4 +1,4 @@
|
|||||||
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg)
|
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
|
||||||
|
|
||||||
![WorkAdventure landscape image](README-INTRO.jpg)
|
![WorkAdventure landscape image](README-INTRO.jpg)
|
||||||
|
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
import { Group } from "./Group";
|
|
||||||
import { PointInterface } from "./Websocket/PointInterface";
|
|
||||||
import {Zone} from "_Model/Zone";
|
|
||||||
import {Movable} from "_Model/Movable";
|
|
||||||
import {PositionNotifier} from "_Model/PositionNotifier";
|
|
||||||
import {ServerDuplexStream} from "grpc";
|
|
||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
@ -11,7 +5,6 @@ import {
|
|||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
|
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
|
||||||
import {AdminSocket} from "../RoomManager";
|
import {AdminSocket} from "../RoomManager";
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,12 +38,10 @@ export class GameRoom {
|
|||||||
|
|
||||||
private readonly positionNotifier: PositionNotifier;
|
private readonly positionNotifier: PositionNotifier;
|
||||||
public readonly roomId: string;
|
public readonly roomId: string;
|
||||||
public readonly anonymous: boolean;
|
|
||||||
public tags: string[];
|
|
||||||
public policyType: GameRoomPolicyTypes;
|
|
||||||
public readonly roomSlug: string;
|
public readonly roomSlug: string;
|
||||||
public readonly worldSlug: string = '';
|
public readonly worldSlug: string = '';
|
||||||
public readonly organizationSlug: string = '';
|
public readonly organizationSlug: string = '';
|
||||||
|
private versionNumber:number = 1;
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(roomId: string,
|
constructor(roomId: string,
|
||||||
@ -56,11 +54,8 @@ export class GameRoom {
|
|||||||
onLeaves: LeavesCallback)
|
onLeaves: LeavesCallback)
|
||||||
{
|
{
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.anonymous = isRoomAnonymous(roomId);
|
|
||||||
this.tags = [];
|
|
||||||
this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY;
|
|
||||||
|
|
||||||
if (this.anonymous) {
|
if (isRoomAnonymous(roomId)) {
|
||||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||||
} else {
|
} else {
|
||||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
||||||
@ -304,10 +299,6 @@ export class GameRoom {
|
|||||||
return this.itemsState;
|
return this.itemsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public canAccess(userTags: string[]): boolean {
|
|
||||||
return arrayIntersect(userTags, this.tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
|
||||||
return this.positionNotifier.addZoneListener(call, x, y);
|
return this.positionNotifier.addZoneListener(call, x, y);
|
||||||
}
|
}
|
||||||
@ -328,4 +319,9 @@ export class GameRoom {
|
|||||||
public adminLeave(admin: Admin): void {
|
public adminLeave(admin: Admin): void {
|
||||||
this.admins.delete(admin);
|
this.admins.delete(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public incrementVersion(): number {
|
||||||
|
this.versionNumber++
|
||||||
|
return this.versionNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,12 @@ import {
|
|||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
QueryJitsiJwtMessage,
|
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
|
||||||
ZoneMessage
|
ZoneMessage
|
||||||
} from "./Messages/generated/messages_pb";
|
} from "./Messages/generated/messages_pb";
|
||||||
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
||||||
@ -43,8 +43,13 @@ const roomManager: IRoomManagerServer = {
|
|||||||
if (room === null || user === null) {
|
if (room === null || user === null) {
|
||||||
if (message.hasJoinroommessage()) {
|
if (message.hasJoinroommessage()) {
|
||||||
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
||||||
room = gameRoom;
|
if (call.writable) {
|
||||||
user = myUser;
|
room = gameRoom;
|
||||||
|
user = myUser;
|
||||||
|
} else {
|
||||||
|
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
||||||
|
socketManager.leaveRoom(gameRoom, myUser);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
||||||
@ -184,6 +189,14 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
},
|
},
|
||||||
|
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
||||||
|
callback(null, new EmptyMessage());
|
||||||
|
},
|
||||||
|
sendRefreshRoomPrompt(call: ServerUnaryCall<RefreshRoomPromptMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
|
socketManager.dispatchRoomRefresh(call.request.getRoomid());
|
||||||
|
callback(null, new EmptyMessage());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export {roomManager};
|
export {roomManager};
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
|
||||||
import Axios from "axios";
|
|
||||||
|
|
||||||
export interface AdminApiData {
|
|
||||||
organizationSlug: string
|
|
||||||
worldSlug: string
|
|
||||||
roomSlug: string
|
|
||||||
mapUrlStart: string
|
|
||||||
tags: string[]
|
|
||||||
policy_type: number
|
|
||||||
userUuid: string
|
|
||||||
messages?: unknown[],
|
|
||||||
textures: CharacterTexture[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CharacterTexture {
|
|
||||||
id: number,
|
|
||||||
level: number,
|
|
||||||
url: string,
|
|
||||||
rights: string
|
|
||||||
}
|
|
||||||
|
|
||||||
class AdminApi {
|
|
||||||
|
|
||||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject('No admin backoffice set!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
|
||||||
organizationSlug,
|
|
||||||
worldSlug
|
|
||||||
};
|
|
||||||
|
|
||||||
if (roomSlug) {
|
|
||||||
params.roomSlug = roomSlug;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await Axios.get(ADMIN_API_URL + '/api/map',
|
|
||||||
{
|
|
||||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
|
|
||||||
params
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const adminApi = new AdminApi();
|
|
@ -24,8 +24,9 @@ import {
|
|||||||
UserJoinedZoneMessage,
|
UserJoinedZoneMessage,
|
||||||
GroupUpdateZoneMessage,
|
GroupUpdateZoneMessage,
|
||||||
GroupLeftZoneMessage,
|
GroupLeftZoneMessage,
|
||||||
|
WorldFullWarningMessage,
|
||||||
UserLeftZoneMessage,
|
UserLeftZoneMessage,
|
||||||
BanUserMessage,
|
BanUserMessage, RefreshRoomMessage,
|
||||||
} 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";
|
||||||
@ -40,7 +41,6 @@ import {
|
|||||||
} from "../Enum/EnvironmentVariable";
|
} 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} 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";
|
||||||
@ -59,6 +59,7 @@ function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): vo
|
|||||||
const batchMessage = new BatchToPusherMessage();
|
const batchMessage = new BatchToPusherMessage();
|
||||||
batchMessage.addPayload(subMessage);
|
batchMessage.addPayload(subMessage);
|
||||||
|
|
||||||
|
|
||||||
socket.write(batchMessage);
|
socket.write(batchMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,13 @@ export class SocketManager {
|
|||||||
//join new previous room
|
//join new previous room
|
||||||
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
||||||
|
|
||||||
|
if (!socket.writable) {
|
||||||
|
console.warn('Socket was aborted');
|
||||||
|
return {
|
||||||
|
room,
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
const roomJoinedMessage = new RoomJoinedMessage();
|
const roomJoinedMessage = new RoomJoinedMessage();
|
||||||
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
||||||
|
|
||||||
@ -94,7 +102,6 @@ export class SocketManager {
|
|||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
||||||
console.log('SENDING MESSAGE roomJoinedMessage');
|
|
||||||
socket.write(serverToClientMessage);
|
socket.write(serverToClientMessage);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -122,14 +129,6 @@ export class SocketManager {
|
|||||||
throw new Error('Viewport not found in message');
|
throw new Error('Viewport not found in message');
|
||||||
}
|
}
|
||||||
|
|
||||||
// sending to all clients in room except sender
|
|
||||||
/*client.position = {
|
|
||||||
x: position.x,
|
|
||||||
y: position.y,
|
|
||||||
direction,
|
|
||||||
moving: position.moving,
|
|
||||||
};
|
|
||||||
client.viewport = viewport;*/
|
|
||||||
|
|
||||||
// update position in the world
|
// update position in the world
|
||||||
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
|
||||||
@ -184,21 +183,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle this message in pusher
|
|
||||||
/*async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
|
||||||
try {
|
|
||||||
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
|
||||||
if (!reportedSocket) {
|
|
||||||
throw 'reported socket user not found';
|
|
||||||
}
|
|
||||||
//TODO report user on admin application
|
|
||||||
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('An error occurred on "handleReportMessage"');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
|
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
|
||||||
//send only at user
|
//send only at user
|
||||||
const remoteUser = room.getUsers().get(data.getReceiverid());
|
const remoteUser = room.getUsers().get(data.getReceiverid());
|
||||||
@ -262,8 +246,6 @@ export class SocketManager {
|
|||||||
debug('Room is empty. Deleting room "%s"', room.roomId);
|
debug('Room is empty. Deleting room "%s"', room.roomId);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
//delete Client.roomId;
|
|
||||||
//this.sockets.delete(Client.userId);
|
|
||||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
||||||
console.log('A user left');
|
console.log('A user left');
|
||||||
}
|
}
|
||||||
@ -283,11 +265,6 @@ export class SocketManager {
|
|||||||
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
||||||
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
|
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
|
||||||
);
|
);
|
||||||
if (!world.anonymous) {
|
|
||||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
|
||||||
world.tags = data.tags
|
|
||||||
world.policyType = Number(data.policy_type)
|
|
||||||
}
|
|
||||||
gaugeManager.incNbRoomGauge();
|
gaugeManager.incNbRoomGauge();
|
||||||
this.rooms.set(roomId, world);
|
this.rooms.set(roomId, world);
|
||||||
}
|
}
|
||||||
@ -298,20 +275,14 @@ export class SocketManager {
|
|||||||
|
|
||||||
const roomId = joinRoomMessage.getRoomid();
|
const roomId = joinRoomMessage.getRoomid();
|
||||||
|
|
||||||
const world = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
|
|
||||||
// Dispatch groups position to newly connected user
|
|
||||||
/*world.getGroups().forEach((group: Group) => {
|
|
||||||
this.emitCreateUpdateGroupEvent(socket, group);
|
|
||||||
});*/
|
|
||||||
|
|
||||||
//join world
|
//join world
|
||||||
const user = world.join(socket, joinRoomMessage);
|
const user = room.join(socket, joinRoomMessage);
|
||||||
|
|
||||||
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
||||||
//console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
|
|
||||||
console.log(new Date().toISOString() + ' A user joined');
|
console.log(new Date().toISOString() + ' A user joined');
|
||||||
return {room: world, user};
|
return {room, user};
|
||||||
}
|
}
|
||||||
|
|
||||||
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
||||||
@ -419,10 +390,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private joinWebRtcRoom(user: User, group: Group) {
|
private joinWebRtcRoom(user: User, group: Group) {
|
||||||
/*const roomId: string = "webrtcroom"+group.getId();
|
|
||||||
if (user.socket.webRtcRoomId === roomId) {
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
for (const otherUser of group.getUsers()) {
|
for (const otherUser of group.getUsers()) {
|
||||||
if (user === otherUser) {
|
if (user === otherUser) {
|
||||||
@ -758,6 +725,43 @@ export class SocketManager {
|
|||||||
recipient.socket.write(clientMessage);
|
recipient.socket.write(clientMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatchWorlFullWarning(roomId: string,): void {
|
||||||
|
const room = this.rooms.get(roomId);
|
||||||
|
if (!room) {
|
||||||
|
//todo: this should cause the http call to return a 500
|
||||||
|
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.getUsers().forEach((recipient) => {
|
||||||
|
const worldFullMessage = new WorldFullWarningMessage();
|
||||||
|
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setWorldfullwarningmessage(worldFullMessage);
|
||||||
|
|
||||||
|
recipient.socket.write(clientMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchRoomRefresh(roomId: string,): void {
|
||||||
|
const room = this.rooms.get(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionNumber = room.incrementVersion();
|
||||||
|
room.getUsers().forEach((recipient) => {
|
||||||
|
const worldFullMessage = new RefreshRoomMessage();
|
||||||
|
worldFullMessage.setRoomid(roomId)
|
||||||
|
worldFullMessage.setVersionnumber(versionNumber)
|
||||||
|
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setRefreshroommessage(worldFullMessage);
|
||||||
|
|
||||||
|
recipient.socket.write(clientMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -3032,9 +3032,9 @@ xtend@^4.0.0:
|
|||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
y18n@^3.2.0:
|
y18n@^3.2.0:
|
||||||
version "3.2.1"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||||
|
|
||||||
yallist@^3.0.0, yallist@^3.0.3:
|
yallist@^3.0.0, yallist@^3.0.3:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
|
@ -37,7 +37,7 @@ services:
|
|||||||
DEBUG_MODE: "$DEBUG_MODE"
|
DEBUG_MODE: "$DEBUG_MODE"
|
||||||
JITSI_URL: $JITSI_URL
|
JITSI_URL: $JITSI_URL
|
||||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||||
API_URL: pusher.${DOMAIN}
|
PUSHER_URL: //pusher.${DOMAIN}
|
||||||
TURN_SERVER: "${TURN_SERVER}"
|
TURN_SERVER: "${TURN_SERVER}"
|
||||||
TURN_USER: "${TURN_USER}"
|
TURN_USER: "${TURN_USER}"
|
||||||
TURN_PASSWORD: "${TURN_PASSWORD}"
|
TURN_PASSWORD: "${TURN_PASSWORD}"
|
||||||
|
@ -82,14 +82,12 @@
|
|||||||
},
|
},
|
||||||
"ports": [80],
|
"ports": [80],
|
||||||
"env": {
|
"env": {
|
||||||
"API_URL": "pusher."+url,
|
"PUSHER_URL": "//pusher."+url,
|
||||||
"UPLOADER_URL": "uploader."+url,
|
"UPLOADER_URL": "//uploader."+url,
|
||||||
"ADMIN_URL": 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_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"
|
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
|
||||||
//"GA_TRACKING_ID": "UA-10196481-11"
|
//"GA_TRACKING_ID": "UA-10196481-11"
|
||||||
|
207
docker-compose.single-domain.yaml
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
reverse-proxy:
|
||||||
|
image: traefik:v2.0
|
||||||
|
command:
|
||||||
|
- --api.insecure=true
|
||||||
|
- --providers.docker
|
||||||
|
- --entryPoints.web.address=:80
|
||||||
|
- --entryPoints.websecure.address=:443
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
# The Web UI (enabled by --api.insecure=true)
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
- back
|
||||||
|
- front
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
front:
|
||||||
|
image: thecodingmachine/nodejs:14
|
||||||
|
environment:
|
||||||
|
DEBUG_MODE: "$DEBUG_MODE"
|
||||||
|
JITSI_URL: $JITSI_URL
|
||||||
|
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
NODE_ENV: development
|
||||||
|
PUSHER_URL: /pusher
|
||||||
|
UPLOADER_URL: /uploader
|
||||||
|
ADMIN_URL: /admin
|
||||||
|
MAPS_URL: /maps
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||||
|
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||||
|
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||||
|
TURN_USER: ""
|
||||||
|
TURN_PASSWORD: ""
|
||||||
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
|
command: yarn run start
|
||||||
|
volumes:
|
||||||
|
- ./front:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.front.rule=PathPrefix(`/`)"
|
||||||
|
- "traefik.http.routers.front.entryPoints=web,traefik"
|
||||||
|
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.front-ssl.rule=PathPrefix(`/`)"
|
||||||
|
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.front-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.front-ssl.service=front"
|
||||||
|
|
||||||
|
pusher:
|
||||||
|
image: thecodingmachine/nodejs:12
|
||||||
|
command: yarn dev
|
||||||
|
#command: yarn run prod
|
||||||
|
#command: yarn run profile
|
||||||
|
environment:
|
||||||
|
DEBUG: "*"
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
|
SECRET_KEY: yourSecretKey
|
||||||
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
|
API_URL: back:50051
|
||||||
|
JITSI_URL: $JITSI_URL
|
||||||
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
volumes:
|
||||||
|
- ./pusher:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher"
|
||||||
|
- "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)"
|
||||||
|
- "traefik.http.routers.pusher.middlewares=strip-pusher-prefix@docker"
|
||||||
|
- "traefik.http.routers.pusher.entryPoints=web"
|
||||||
|
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.pusher-ssl.rule=PathPrefix(`/pusher`)"
|
||||||
|
- "traefik.http.routers.pusher-ssl.middlewares=strip-pusher-prefix@docker"
|
||||||
|
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.pusher-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.pusher-ssl.service=pusher"
|
||||||
|
|
||||||
|
maps:
|
||||||
|
image: thecodingmachine/nodejs:12-apache
|
||||||
|
environment:
|
||||||
|
DEBUG_MODE: "$DEBUG_MODE"
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
NODE_ENV: development
|
||||||
|
#APACHE_DOCUMENT_ROOT: dist/
|
||||||
|
#APACHE_EXTENSIONS: headers
|
||||||
|
#APACHE_EXTENSION_HEADERS: 1
|
||||||
|
STARTUP_COMMAND_0: sudo a2enmod headers
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
STARTUP_COMMAND_2: yarn run dev &
|
||||||
|
volumes:
|
||||||
|
- ./maps:/var/www/html
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-maps-prefix.stripprefix.prefixes=/maps"
|
||||||
|
- "traefik.http.routers.maps.rule=PathPrefix(`/maps`)"
|
||||||
|
- "traefik.http.routers.maps.middlewares=strip-maps-prefix@docker"
|
||||||
|
- "traefik.http.routers.maps.entryPoints=web,traefik"
|
||||||
|
- "traefik.http.services.maps.loadbalancer.server.port=80"
|
||||||
|
- "traefik.http.routers.maps-ssl.rule=PathPrefix(`/maps`)"
|
||||||
|
- "traefik.http.routers.maps-ssl.middlewares=strip-maps-prefix@docker"
|
||||||
|
- "traefik.http.routers.maps-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.maps-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.maps-ssl.service=maps"
|
||||||
|
|
||||||
|
back:
|
||||||
|
image: thecodingmachine/nodejs:12
|
||||||
|
command: yarn dev
|
||||||
|
#command: yarn run profile
|
||||||
|
environment:
|
||||||
|
DEBUG: "*"
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
SECRET_KEY: yourSecretKey
|
||||||
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
|
ALLOW_ARTILLERY: "true"
|
||||||
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
|
JITSI_URL: $JITSI_URL
|
||||||
|
JITSI_ISS: $JITSI_ISS
|
||||||
|
volumes:
|
||||||
|
- ./back:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
|
||||||
|
- "traefik.http.routers.back.rule=PathPrefix(`/api`)"
|
||||||
|
- "traefik.http.routers.back.middlewares=strip-api-prefix@docker"
|
||||||
|
- "traefik.http.routers.back.entryPoints=web"
|
||||||
|
- "traefik.http.services.back.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.back-ssl.rule=PathPrefix(`/api`)"
|
||||||
|
- "traefik.http.routers.back-ssl.middlewares=strip-api-prefix@docker"
|
||||||
|
- "traefik.http.routers.back-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.back-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.back-ssl.service=back"
|
||||||
|
|
||||||
|
uploader:
|
||||||
|
image: thecodingmachine/nodejs:12
|
||||||
|
command: yarn dev
|
||||||
|
#command: yarn run profile
|
||||||
|
environment:
|
||||||
|
DEBUG: "*"
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
volumes:
|
||||||
|
- ./uploader:/usr/src/app
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.strip-uploader-prefix.stripprefix.prefixes=/uploader"
|
||||||
|
- "traefik.http.routers.uploader.rule=PathPrefix(`/uploader`)"
|
||||||
|
- "traefik.http.routers.uploader.middlewares=strip-uploader-prefix@docker"
|
||||||
|
- "traefik.http.routers.uploader.entryPoints=web"
|
||||||
|
- "traefik.http.services.uploader.loadbalancer.server.port=8080"
|
||||||
|
- "traefik.http.routers.uploader-ssl.rule=PathPrefix(`/uploader`)"
|
||||||
|
- "traefik.http.routers.uploader-ssl.middlewares=strip-uploader-prefix@docker"
|
||||||
|
- "traefik.http.routers.uploader-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.uploader-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.uploader-ssl.service=uploader"
|
||||||
|
|
||||||
|
website:
|
||||||
|
image: thecodingmachine/nodejs:12-apache
|
||||||
|
environment:
|
||||||
|
STARTUP_COMMAND_1: npm install
|
||||||
|
STARTUP_COMMAND_2: npm run watch &
|
||||||
|
APACHE_DOCUMENT_ROOT: dist/
|
||||||
|
volumes:
|
||||||
|
- ./website:/var/www/html
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
|
||||||
|
- "traefik.http.routers.website.entryPoints=web"
|
||||||
|
- "traefik.http.services.website.loadbalancer.server.port=80"
|
||||||
|
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
|
||||||
|
- "traefik.http.routers.website-ssl.entryPoints=websecure"
|
||||||
|
- "traefik.http.routers.website-ssl.tls=true"
|
||||||
|
- "traefik.http.routers.website-ssl.service=website"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
#image: thecodingmachine/nodejs:14
|
||||||
|
image: thecodingmachine/workadventure-back-base:latest
|
||||||
|
environment:
|
||||||
|
#STARTUP_COMMAND_0: sudo apt-get install -y inotify-tools
|
||||||
|
STARTUP_COMMAND_1: yarn install
|
||||||
|
STARTUP_COMMAND_2: yarn run proto:watch
|
||||||
|
volumes:
|
||||||
|
- ./messages:/usr/src/app
|
||||||
|
- ./back:/usr/src/back
|
||||||
|
- ./front:/usr/src/front
|
||||||
|
- ./pusher:/usr/src/pusher
|
||||||
|
|
||||||
|
# coturn:
|
||||||
|
# image: coturn/coturn:4.5.2
|
||||||
|
# command:
|
||||||
|
# - turnserver
|
||||||
|
# #- -c=/etc/coturn/turnserver.conf
|
||||||
|
# - --log-file=stdout
|
||||||
|
# - --external-ip=$$(detect-external-ip)
|
||||||
|
# - --listening-port=3478
|
||||||
|
# - --min-port=10000
|
||||||
|
# - --max-port=10010
|
||||||
|
# - --tls-listening-port=5349
|
||||||
|
# - --listening-ip=0.0.0.0
|
||||||
|
# - --realm=localhost
|
||||||
|
# - --server-name=localhost
|
||||||
|
# - --lt-cred-mech
|
||||||
|
# # Enable Coturn "REST API" to validate temporary passwords.
|
||||||
|
# #- --use-auth-secret
|
||||||
|
# #- --static-auth-secret=SomeStaticAuthSecret
|
||||||
|
# #- --userdb=/var/lib/turn/turndb
|
||||||
|
# - --user=workadventure:WorkAdventure123
|
||||||
|
# # use real-valid certificate/privatekey files
|
||||||
|
# #- --cert=/root/letsencrypt/fullchain.pem
|
||||||
|
# #- --pkey=/root/letsencrypt/privkey.pem
|
||||||
|
# network_mode: host
|
@ -26,9 +26,9 @@ services:
|
|||||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||||
HOST: "0.0.0.0"
|
HOST: "0.0.0.0"
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
API_URL: pusher.workadventure.localhost
|
PUSHER_URL: //pusher.workadventure.localhost
|
||||||
UPLOADER_URL: uploader.workadventure.localhost
|
UPLOADER_URL: //uploader.workadventure.localhost
|
||||||
ADMIN_URL: workadventure.localhost
|
ADMIN_URL: //workadventure.localhost
|
||||||
STARTUP_COMMAND_1: ./templater.sh
|
STARTUP_COMMAND_1: ./templater.sh
|
||||||
STARTUP_COMMAND_2: yarn install
|
STARTUP_COMMAND_2: yarn install
|
||||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||||
@ -43,7 +43,7 @@ services:
|
|||||||
- ./front:/usr/src/app
|
- ./front:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
||||||
- "traefik.http.routers.front.entryPoints=web,traefik"
|
- "traefik.http.routers.front.entryPoints=web"
|
||||||
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||||
- "traefik.http.routers.front-ssl.rule=Host(`play.workadventure.localhost`)"
|
- "traefik.http.routers.front-ssl.rule=Host(`play.workadventure.localhost`)"
|
||||||
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||||
@ -53,10 +53,8 @@ services:
|
|||||||
pusher:
|
pusher:
|
||||||
image: thecodingmachine/nodejs:12
|
image: thecodingmachine/nodejs:12
|
||||||
command: yarn dev
|
command: yarn dev
|
||||||
#command: yarn run prod
|
|
||||||
#command: yarn run profile
|
|
||||||
environment:
|
environment:
|
||||||
DEBUG: "*"
|
DEBUG: "socket:*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
|
@ -3,11 +3,16 @@ WORKDIR /var/www/messages
|
|||||||
COPY --chown=docker:docker messages .
|
COPY --chown=docker:docker messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
# we are rebuilding on each deploy to cope with the API_URL environment URL
|
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
|
||||||
FROM thecodingmachine/nodejs:14-apache
|
FROM thecodingmachine/nodejs:14-apache
|
||||||
|
|
||||||
COPY --chown=docker:docker front .
|
COPY --chown=docker:docker front .
|
||||||
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
|
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
|
||||||
|
|
||||||
|
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
||||||
|
# iframe.html is only in dev mode to circumvent a limitation
|
||||||
|
RUN rm dist/iframe.html
|
||||||
|
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
1
front/dist/.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
index.html
|
index.html
|
||||||
index.tmpl.html.tmp
|
index.tmpl.html.tmp
|
||||||
|
/js/
|
||||||
style.*.css
|
style.*.css
|
17
front/dist/iframe.html
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script src="/iframe_api.js" ></script>
|
||||||
|
<script>
|
||||||
|
// Note: this is a huge XSS flow as we allow anyone to load a Javascript file in our domain.
|
||||||
|
// This file must ABSOLUTELY be removed from the Docker images/deployments and is only here
|
||||||
|
// for development purpose (because dynamically generated iframes are not working with
|
||||||
|
// webpack hot reload due to an issue with rights)
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const scriptUrl = urlParams.get('script');
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = scriptUrl;
|
||||||
|
document.head.append(script);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
15
front/dist/index.tmpl.html
vendored
@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||||
|
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
<title>WorkAdventure</title>
|
<title>WorkAdventure</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="body" style="margin: 0; background-color: #000">
|
<body id="body" style="margin: 0; background-color: #000">
|
||||||
@ -70,11 +73,11 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<main id="cowebsite-main">
|
<main id="cowebsite-main">
|
||||||
</main>
|
</main>
|
||||||
<button class="top-right-btn" id="cowebsite-fullscreen">
|
<button class="top-right-btn" id="cowebsite-fullscreen" alt="fullscreen mode">
|
||||||
<img id="cowebsite-fullscreen-open" src="resources/logos/monitor.svg"/>
|
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
||||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/monitor-close.svg"/>
|
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="top-right-btn" id="cowebsite-close">
|
<button class="top-right-btn" id="cowebsite-close" alt="close the iframe">
|
||||||
<img src="resources/logos/close.svg"/>
|
<img src="resources/logos/close.svg"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -92,12 +95,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="audioplayer">
|
<div class="audioplayer">
|
||||||
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.05" value="1" />
|
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.025" value="1" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="audioplayer">
|
<div class="audioplayer">
|
||||||
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
|
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
|
||||||
autoreduce
|
reduce in conversations
|
||||||
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
||||||
</label>
|
</label>
|
||||||
<div id="audioplayer" style="visibility: hidden"></div>
|
<div id="audioplayer" style="visibility: hidden"></div>
|
||||||
|
18
front/dist/resources/html/warningContainer.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<style>
|
||||||
|
#warningMain {
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 100px;
|
||||||
|
width: 300px;
|
||||||
|
background-color: red;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#warningMain h2 {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<main id="warningMain">
|
||||||
|
<h2>Warning!</h2>
|
||||||
|
<p>This world is close to its limit!</p>
|
||||||
|
</main>
|
3
front/dist/resources/logos/fullscreen-exit.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="i-fullscreen-exit" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path d="M4 12 L12 12 12 4 M20 4 L20 12 28 12 M4 20 L12 20 12 28 M28 20 L20 20 20 28" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 329 B |
3
front/dist/resources/logos/fullscreen.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="i-fullscreen" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path d="M4 12 L4 4 12 4 M20 4 L28 4 28 12 M4 20 L4 28 12 28 M28 20 L28 28 20 28" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 322 B |
66
front/dist/resources/style/cowebsite.scss
vendored
@ -35,9 +35,8 @@
|
|||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 20px;
|
height: 25px;
|
||||||
background-color: rgba(0,0.0,0,0.3);
|
padding: 4px;
|
||||||
padding: 5px;
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,31 +77,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.top-right-btn{
|
.top-right-btn{
|
||||||
top: 10px;
|
left: -6px;
|
||||||
right: -100px;
|
&#cowebsite-close {
|
||||||
animation: right .2s ease;
|
top: 0px;
|
||||||
|
}
|
||||||
img {
|
&#cowebsite-fullscreen {
|
||||||
right: 15px;
|
top: 25px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite-close {
|
|
||||||
right: -140px;
|
|
||||||
}
|
|
||||||
#cowebsite-fullscreen {
|
|
||||||
right: -100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite:hover {
|
|
||||||
#cowebsite-close{
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite-fullscreen{
|
|
||||||
right: 45px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,32 +120,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-right-btn{
|
.top-right-btn {
|
||||||
top: 10px;
|
&#cowebsite-close {
|
||||||
right: -100px;
|
right: 0px;
|
||||||
animation: right .2s ease;
|
}
|
||||||
|
&#cowebsite-fullscreen {
|
||||||
img {
|
right: 25px;
|
||||||
right: 15px;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite-close {
|
|
||||||
right: -140px;
|
|
||||||
}
|
|
||||||
#cowebsite-fullscreen {
|
|
||||||
right: -100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite:hover {
|
|
||||||
#cowebsite-close{
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cowebsite-fullscreen{
|
|
||||||
right: 45px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
31
front/dist/resources/style/style.css
vendored
@ -363,10 +363,14 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audioplayer > div {
|
||||||
|
padding-right: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
#audioplayerctrl {
|
#audioplayerctrl {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 50%;
|
right: calc(50% - 120px);
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
color: white;
|
color: white;
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
@ -1119,6 +1123,31 @@ div.action p.action-body{
|
|||||||
margin-left: calc(50% - 75px);
|
margin-left: calc(50% - 75px);
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
.popUpElement{
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
text-align: left;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.popUpElement div {
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
font-size: 10px;
|
||||||
|
background-color: #727678;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popUpElement button {
|
||||||
|
position: relative;
|
||||||
|
font-size: 10px;
|
||||||
|
border-image-repeat: revert;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popUpElement .buttonContainer {
|
||||||
|
float: right;
|
||||||
|
background-color: inherit;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes mymove {
|
@keyframes mymove {
|
||||||
0% {bottom: 40px;}
|
0% {bottom: 40px;}
|
||||||
50% {bottom: 30px;}
|
50% {bottom: 30px;}
|
||||||
|
@ -336,7 +336,7 @@ export class ConsoleGlobalMessageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
active(){
|
active(){
|
||||||
this.userInputManager.clearAllKeys();
|
this.userInputManager.disableControls();
|
||||||
this.divMainConsole.style.top = '0';
|
this.divMainConsole.style.top = '0';
|
||||||
this.activeConsole = true;
|
this.activeConsole = true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||||
import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
|
11
front/src/Api/Events/ButtonClickedEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isButtonClickedEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
popupId: tg.isNumber,
|
||||||
|
buttonId: tg.isNumber,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||||
|
*/
|
||||||
|
export type ButtonClickedEvent = tg.GuardedType<typeof isButtonClickedEvent>;
|
11
front/src/Api/Events/ChatEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isChatEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
message: tg.isString,
|
||||||
|
author: tg.isString,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type ChatEvent = tg.GuardedType<typeof isChatEvent>;
|
11
front/src/Api/Events/ClosePopupEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isClosePopupEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
popupId: tg.isNumber,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type ClosePopupEvent = tg.GuardedType<typeof isClosePopupEvent>;
|
10
front/src/Api/Events/EnterLeaveEvent.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isEnterLeaveEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||||
|
*/
|
||||||
|
export type EnterLeaveEvent = tg.GuardedType<typeof isEnterLeaveEvent>;
|
13
front/src/Api/Events/GoToPageEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const isGoToPageEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type GoToPageEvent = tg.GuardedType<typeof isGoToPageEvent>;
|
7
front/src/Api/Events/IframeEvent.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface IframeEvent {
|
||||||
|
type: string;
|
||||||
|
data: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string';
|
13
front/src/Api/Events/OpenCoWebSiteEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const isOpenCoWebsite =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type OpenCoWebSiteEvent = tg.GuardedType<typeof isOpenCoWebsite>;
|
20
front/src/Api/Events/OpenPopupEvent.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
const isButtonDescriptor =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
label: tg.isString,
|
||||||
|
className: tg.isOptional(tg.isString)
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
export const isOpenPopupEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
popupId: tg.isNumber,
|
||||||
|
targetObject: tg.isString,
|
||||||
|
message: tg.isString,
|
||||||
|
buttons: tg.isArray(isButtonDescriptor)
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type OpenPopupEvent = tg.GuardedType<typeof isOpenPopupEvent>;
|
13
front/src/Api/Events/OpenTabEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const isOpenTabEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type OpenTabEvent = tg.GuardedType<typeof isOpenTabEvent>;
|
10
front/src/Api/Events/UserInputChatEvent.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isUserInputChatEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
message: tg.isString,
|
||||||
|
}).get();
|
||||||
|
/**
|
||||||
|
* A message sent from the game to the iFrame when a user types a message in the chat.
|
||||||
|
*/
|
||||||
|
export type UserInputChatEvent = tg.GuardedType<typeof isUserInputChatEvent>;
|
238
front/src/Api/IframeListener.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
||||||
|
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
|
||||||
|
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
|
import {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
||||||
|
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
||||||
|
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
||||||
|
import {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
||||||
|
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
||||||
|
import {scriptUtils} from "./ScriptUtils";
|
||||||
|
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
||||||
|
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
|
* Also allows to send messages to those iframes.
|
||||||
|
*/
|
||||||
|
class IframeListener {
|
||||||
|
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||||
|
public readonly chatStream = this._chatStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _openPopupStream: Subject<OpenPopupEvent> = new Subject();
|
||||||
|
public readonly openPopupStream = this._openPopupStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _openTabStream: Subject<OpenTabEvent> = new Subject();
|
||||||
|
public readonly openTabStream = this._openTabStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _goToPageStream: Subject<GoToPageEvent> = new Subject();
|
||||||
|
public readonly goToPageStream = this._goToPageStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _openCoWebSiteStream: Subject<OpenCoWebSiteEvent> = new Subject();
|
||||||
|
public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _closeCoWebSiteStream: Subject<void> = new Subject();
|
||||||
|
public readonly closeCoWebSiteStream = this._closeCoWebSiteStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
||||||
|
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
||||||
|
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _closePopupStream: Subject<ClosePopupEvent> = new Subject();
|
||||||
|
public readonly closePopupStream = this._closePopupStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _displayBubbleStream: Subject<void> = new Subject();
|
||||||
|
public readonly displayBubbleStream = this._displayBubbleStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _removeBubbleStream: Subject<void> = new Subject();
|
||||||
|
public readonly removeBubbleStream = this._removeBubbleStream.asObservable();
|
||||||
|
|
||||||
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
|
|
||||||
|
init() {
|
||||||
|
window.addEventListener("message", (message) => {
|
||||||
|
// Do we trust the sender of this message?
|
||||||
|
// Let's only accept messages from the iframe that are allowed.
|
||||||
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
|
let found = false;
|
||||||
|
for (const iframe of this.iframes) {
|
||||||
|
if (iframe.contentWindow === message.source) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = message.data;
|
||||||
|
if (isIframeEventWrapper(payload)) {
|
||||||
|
if (payload.type === 'chat' && isChatEvent(payload.data)) {
|
||||||
|
this._chatStream.next(payload.data);
|
||||||
|
} else if (payload.type === 'openPopup' && isOpenPopupEvent(payload.data)) {
|
||||||
|
this._openPopupStream.next(payload.data);
|
||||||
|
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
||||||
|
this._closePopupStream.next(payload.data);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
||||||
|
scriptUtils.openTab(payload.data.url);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||||
|
scriptUtils.goToPage(payload.data.url);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||||
|
scriptUtils.openCoWebsite(payload.data.url);
|
||||||
|
}
|
||||||
|
else if(payload.type === 'closeCoWebSite') {
|
||||||
|
scriptUtils.closeCoWebSite();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'disablePlayerControl'){
|
||||||
|
this._disablePlayerControlStream.next();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'restorePlayerControl'){
|
||||||
|
this._enablePlayerControlStream.next();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'displayBubble'){
|
||||||
|
this._displayBubbleStream.next();
|
||||||
|
}
|
||||||
|
else if (payload.type === 'removeBubble'){
|
||||||
|
this._removeBubbleStream.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the passed iFrame to send/receive messages via the API.
|
||||||
|
*/
|
||||||
|
registerIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
this.iframes.add(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
this.iframes.delete(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerScript(scriptUrl: string): void {
|
||||||
|
console.log('Loading map related script at ', scriptUrl)
|
||||||
|
|
||||||
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||||
|
// Using external iframe mode (
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.id = this.getIFrameId(scriptUrl);
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe.src = '/iframe.html?script='+encodeURIComponent(scriptUrl);
|
||||||
|
|
||||||
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
|
iframe.sandbox.add('allow-scripts');
|
||||||
|
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||||
|
|
||||||
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
|
this.scripts.set(scriptUrl, iframe);
|
||||||
|
this.registerIframe(iframe);
|
||||||
|
} else {
|
||||||
|
// production code
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.id = this.getIFrameId(scriptUrl);
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
|
||||||
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
|
iframe.sandbox.add('allow-scripts');
|
||||||
|
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||||
|
|
||||||
|
const html = '<!doctype html>\n' +
|
||||||
|
'\n' +
|
||||||
|
'<html lang="en">\n' +
|
||||||
|
'<head>\n' +
|
||||||
|
'<script src="'+window.location.protocol+'//'+window.location.host+'/iframe_api.js" ></script>\n' +
|
||||||
|
'<script src="'+scriptUrl+'" ></script>\n' +
|
||||||
|
'</head>\n' +
|
||||||
|
'</html>\n';
|
||||||
|
|
||||||
|
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||||
|
iframe.srcdoc = html;
|
||||||
|
|
||||||
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
|
this.scripts.set(scriptUrl, iframe);
|
||||||
|
this.registerIframe(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIFrameId(scriptUrl: string): string {
|
||||||
|
return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterScript(scriptUrl: string): void {
|
||||||
|
const iFrameId = this.getIFrameId(scriptUrl);
|
||||||
|
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||||
|
if (!iframe) {
|
||||||
|
throw new Error('Unknown iframe for script "'+scriptUrl+'"');
|
||||||
|
}
|
||||||
|
this.unregisterIframe(iframe);
|
||||||
|
iframe.remove();
|
||||||
|
|
||||||
|
this.scripts.delete(scriptUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendUserInputChat(message: string) {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'userInputChat',
|
||||||
|
'data': {
|
||||||
|
'message': message,
|
||||||
|
} as UserInputChatEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEnterEvent(name: string) {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'enterEvent',
|
||||||
|
'data': {
|
||||||
|
"name": name
|
||||||
|
} as EnterLeaveEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLeaveEvent(name: string) {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'leaveEvent',
|
||||||
|
'data': {
|
||||||
|
"name": name
|
||||||
|
} as EnterLeaveEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
||||||
|
this.postMessage({
|
||||||
|
'type': 'buttonClickedEvent',
|
||||||
|
'data': {
|
||||||
|
popupId,
|
||||||
|
buttonId
|
||||||
|
} as ButtonClickedEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the message... to all allowed iframes.
|
||||||
|
*/
|
||||||
|
private postMessage(message: IframeEvent) {
|
||||||
|
for (const iframe of this.iframes) {
|
||||||
|
iframe.contentWindow?.postMessage(message, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const iframeListener = new IframeListener();
|
23
front/src/Api/ScriptUtils.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {coWebsiteManager} from "../WebRtc/CoWebsiteManager";
|
||||||
|
|
||||||
|
class ScriptUtils {
|
||||||
|
|
||||||
|
public openTab(url : string){
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public goToPage(url : string){
|
||||||
|
window.location.href = url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public openCoWebsite(url : string){
|
||||||
|
coWebsiteManager.loadCoWebsite(url,url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeCoWebSite(){
|
||||||
|
coWebsiteManager.closeCoWebsite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const scriptUtils = new ScriptUtils();
|
@ -1,27 +1,30 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {API_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_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";
|
||||||
import {localUserStore} from "./LocalUserStore";
|
import {localUserStore} from "./LocalUserStore";
|
||||||
import {LocalUser} from "./LocalUser";
|
import {LocalUser} from "./LocalUser";
|
||||||
import {Room} from "./Room";
|
import {Room} from "./Room";
|
||||||
import {Subject} from "rxjs";
|
|
||||||
|
|
||||||
export enum ConnexionMessageEventTypes {
|
|
||||||
worldFull = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConnexionMessageEvent {
|
|
||||||
type: ConnexionMessageEventTypes,
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!:LocalUser;
|
private localUser!:LocalUser;
|
||||||
|
|
||||||
private connexionType?: GameConnexionTypes
|
private connexionType?: GameConnexionTypes
|
||||||
|
private reconnectingTimeout: NodeJS.Timeout|null = null;
|
||||||
|
private _unloading:boolean = false;
|
||||||
|
|
||||||
public _connexionMessageStream:Subject<ConnexionMessageEvent> = new Subject();
|
get unloading () {
|
||||||
|
return this._unloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
this._unloading = true;
|
||||||
|
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout)
|
||||||
|
})
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Tries to login to the node server and return the starting map url to be loaded
|
* Tries to login to the node server and return the starting map url to be loaded
|
||||||
*/
|
*/
|
||||||
@ -31,7 +34,7 @@ class ConnectionManager {
|
|||||||
this.connexionType = connexionType;
|
this.connexionType = connexionType;
|
||||||
if(connexionType === GameConnexionTypes.register) {
|
if(connexionType === GameConnexionTypes.register) {
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
const data = await Axios.post(`${PUSHER_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
||||||
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ class ConnectionManager {
|
|||||||
const worldSlug = data.worldSlug;
|
const worldSlug = data.worldSlug;
|
||||||
const roomSlug = data.roomSlug;
|
const roomSlug = data.roomSlug;
|
||||||
|
|
||||||
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.hash);
|
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.search + window.location.hash);
|
||||||
urlManager.pushRoomIdToUrl(room);
|
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) {
|
||||||
@ -61,20 +64,20 @@ class ConnectionManager {
|
|||||||
if (connexionType === GameConnexionTypes.empty) {
|
if (connexionType === GameConnexionTypes.empty) {
|
||||||
roomId = START_ROOM_URL;
|
roomId = START_ROOM_URL;
|
||||||
} else {
|
} else {
|
||||||
roomId = window.location.pathname + window.location.hash;
|
roomId = window.location.pathname + window.location.search + window.location.hash;
|
||||||
}
|
}
|
||||||
return Promise.resolve(new Room(roomId));
|
return Promise.resolve(new Room(roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject('Invalid URL');
|
return Promise.reject(new Error('Invalid URL'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async verifyToken(token: string): Promise<void> {
|
private async verifyToken(token: string): Promise<void> {
|
||||||
await Axios.get(`${API_URL}/verify`, {params: {token}});
|
await Axios.get(`${PUSHER_URL}/verify`, {params: {token}});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||||
const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data);
|
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then(res => res.data);
|
||||||
this.localUser = new LocalUser(data.userUuid, data.authToken, []);
|
this.localUser = new LocalUser(data.userUuid, data.authToken, []);
|
||||||
if (!isBenchmark) { // In benchmark, we don't have a local storage.
|
if (!isBenchmark) { // In benchmark, we don't have a local storage.
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
@ -105,7 +108,7 @@ class ConnectionManager {
|
|||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// Let's retry in 4-6 seconds
|
// Let's retry in 4-6 seconds
|
||||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||||
setTimeout(() => {
|
this.reconnectingTimeout = setTimeout(() => {
|
||||||
//todo: allow a way to break recursion?
|
//todo: allow a way to break recursion?
|
||||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import {LocalUser} from "./LocalUser";
|
import {LocalUser} from "./LocalUser";
|
||||||
|
|
||||||
const characterLayersKey = 'characterLayers';
|
const playerNameKey = 'playerName';
|
||||||
const gameQualityKey = 'gameQuality';
|
const selectedPlayerKey = 'selectedPlayer';
|
||||||
const videoQualityKey = 'videoQuality';
|
const customCursorPositionKey = 'customCursorPosition';
|
||||||
|
const characterLayersKey = 'characterLayers';
|
||||||
|
const gameQualityKey = 'gameQuality';
|
||||||
|
const videoQualityKey = 'videoQuality';
|
||||||
|
const audioPlayerVolumeKey = 'audioVolume';
|
||||||
|
const audioPlayerMuteKey = 'audioMute';
|
||||||
|
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||||
|
|
||||||
//todo: add localstorage fallback
|
|
||||||
class LocalUserStore {
|
class LocalUserStore {
|
||||||
|
|
||||||
saveUser(localUser: LocalUser) {
|
saveUser(localUser: LocalUser) {
|
||||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
localStorage.setItem('localUser', JSON.stringify(localUser));
|
||||||
}
|
}
|
||||||
@ -16,46 +20,67 @@ class LocalUserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setName(name:string): void {
|
setName(name:string): void {
|
||||||
window.localStorage.setItem('playerName', name);
|
localStorage.setItem(playerNameKey, name);
|
||||||
}
|
}
|
||||||
getName(): string {
|
getName(): string {
|
||||||
return window.localStorage.getItem('playerName') ?? '';
|
return localStorage.getItem(playerNameKey) || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||||
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
|
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
||||||
}
|
}
|
||||||
getPlayerCharacterIndex(): number {
|
getPlayerCharacterIndex(): number {
|
||||||
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
|
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
||||||
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
|
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
|
||||||
}
|
}
|
||||||
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
||||||
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
|
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCharacterLayers(layers: string[]): void {
|
setCharacterLayers(layers: string[]): void {
|
||||||
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||||
}
|
}
|
||||||
getCharacterLayers(): string[]|null {
|
getCharacterLayers(): string[]|null {
|
||||||
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
|
return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
getGameQualityValue(): number {
|
|
||||||
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
|
|
||||||
}
|
|
||||||
setGameQualityValue(value: number): void {
|
setGameQualityValue(value: number): void {
|
||||||
localStorage.setItem(gameQualityKey, '' + value);
|
localStorage.setItem(gameQualityKey, '' + value);
|
||||||
}
|
}
|
||||||
|
getGameQualityValue(): number {
|
||||||
getVideoQualityValue(): number {
|
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
||||||
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoQualityValue(value: number): void {
|
setVideoQualityValue(value: number): void {
|
||||||
localStorage.setItem(videoQualityKey, '' + value);
|
localStorage.setItem(videoQualityKey, '' + value);
|
||||||
}
|
}
|
||||||
|
getVideoQualityValue(): number {
|
||||||
|
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
||||||
|
}
|
||||||
|
|
||||||
|
setAudioPlayerVolume(value: number): void {
|
||||||
|
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
||||||
|
}
|
||||||
|
getAudioPlayerVolume(): number {
|
||||||
|
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
setAudioPlayerMuted(value: boolean): void {
|
||||||
|
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||||
|
}
|
||||||
|
getAudioPlayerMuted(): boolean {
|
||||||
|
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
setHelpCameraSettingsShown(): void {
|
||||||
|
localStorage.setItem(helpCameraSettingsShown, '1');
|
||||||
|
}
|
||||||
|
getHelpCameraSettingsShown(): boolean {
|
||||||
|
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localUserStore = new LocalUserStore();
|
export const localUserStore = new LocalUserStore();
|
@ -1,29 +1,30 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {API_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export class Room {
|
export class Room {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private mapUrl: string|undefined;
|
private mapUrl: string|undefined;
|
||||||
private instance: string|undefined;
|
private instance: string|undefined;
|
||||||
|
private _search: URLSearchParams;
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
if (id.startsWith('/')) {
|
const url = new URL(id, 'https://example.com');
|
||||||
id = id.substr(1);
|
|
||||||
|
this.id = url.pathname;
|
||||||
|
|
||||||
|
if (this.id.startsWith('/')) {
|
||||||
|
this.id = this.id.substr(1);
|
||||||
}
|
}
|
||||||
this.id = id;
|
if (this.id.startsWith('_/')) {
|
||||||
if (id.startsWith('_/')) {
|
|
||||||
this.isPublic = true;
|
this.isPublic = true;
|
||||||
} else if (id.startsWith('@/')) {
|
} else if (this.id.startsWith('@/')) {
|
||||||
this.isPublic = false;
|
this.isPublic = false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid room ID');
|
throw new Error('Invalid room ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexOfHash = this.id.indexOf('#');
|
this._search = new URLSearchParams(url.search);
|
||||||
if (indexOfHash !== -1) {
|
|
||||||
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} {
|
||||||
@ -66,7 +67,7 @@ export class Room {
|
|||||||
// We have a private ID, we need to query the map URL from the server.
|
// We have a private ID, we need to query the map URL from the server.
|
||||||
const urlParts = this.parsePrivateUrl(this.id);
|
const urlParts = this.parsePrivateUrl(this.id);
|
||||||
|
|
||||||
Axios.get(`${API_URL}/map`, {
|
Axios.get(`${PUSHER_URL}/map`, {
|
||||||
params: urlParts
|
params: urlParts
|
||||||
}).then(({data}) => {
|
}).then(({data}) => {
|
||||||
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
||||||
@ -117,4 +118,17 @@ export class Room {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isDisconnected(): boolean
|
||||||
|
{
|
||||||
|
const alone = this._search.get('alone');
|
||||||
|
if (alone && alone !== '0' && alone.toLowerCase() !== 'false') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get search(): URLSearchParams {
|
||||||
|
return this._search;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {API_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
@ -43,7 +43,9 @@ import {
|
|||||||
} from "./ConnexionModels";
|
} from "./ConnexionModels";
|
||||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||||
import {adminMessagesService} from "./AdminMessagesService";
|
import {adminMessagesService} from "./AdminMessagesService";
|
||||||
import {connectionManager, ConnexionMessageEventTypes} from "./ConnectionManager";
|
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||||
|
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||||
|
import {connectionManager} from "./ConnectionManager";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -65,8 +67,12 @@ export class RoomConnection implements RoomConnection {
|
|||||||
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
||||||
*/
|
*/
|
||||||
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) {
|
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) {
|
||||||
let url = API_URL.replace('http://', 'ws://').replace('https://', 'wss://');
|
let url = new URL(PUSHER_URL, window.location.toString()).toString();
|
||||||
url += '/room';
|
url = url.replace('http://', 'ws://').replace('https://', 'wss://');
|
||||||
|
if (!url.endsWith('/')) {
|
||||||
|
url += '/';
|
||||||
|
}
|
||||||
|
url += 'room';
|
||||||
url += '?roomId='+(roomId ?encodeURIComponent(roomId):'');
|
url += '?roomId='+(roomId ?encodeURIComponent(roomId):'');
|
||||||
url += '&token='+(token ?encodeURIComponent(token):'');
|
url += '&token='+(token ?encodeURIComponent(token):'');
|
||||||
url += '&name='+encodeURIComponent(name);
|
url += '&name='+encodeURIComponent(name);
|
||||||
@ -156,8 +162,8 @@ export class RoomConnection implements RoomConnection {
|
|||||||
items
|
items
|
||||||
} as RoomJoinedMessageInterface
|
} as RoomJoinedMessageInterface
|
||||||
});
|
});
|
||||||
} else if (message.hasErrormessage()) {
|
} else if (message.hasWorldfullmessage()) {
|
||||||
connectionManager._connexionMessageStream.next({type: ConnexionMessageEventTypes.worldFull}); //todo: generalize this behavior to all messages
|
worldFullMessageStream.onMessage();
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||||
@ -179,6 +185,10 @@ export class RoomConnection implements RoomConnection {
|
|||||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
||||||
} else if (message.hasBanusermessage()) {
|
} else if (message.hasBanusermessage()) {
|
||||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
||||||
|
} else if (message.hasWorldfullwarningmessage()) {
|
||||||
|
worldFullWarningStream.onMessage();
|
||||||
|
} else if (message.hasRefreshroommessage()) {
|
||||||
|
//todo: implement a way to notify the user the room was refreshed.
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown message received');
|
throw new Error('Unknown message received');
|
||||||
}
|
}
|
||||||
@ -378,9 +388,6 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.addEventListener('error', callback)
|
this.socket.addEventListener('error', callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public onConnect(callback: (e: Event) => void): void {
|
|
||||||
this.socket.addEventListener('open', callback)
|
|
||||||
}*/
|
|
||||||
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
||||||
//this.socket.addEventListener('open', callback)
|
//this.socket.addEventListener('open', callback)
|
||||||
this.onMessage(EventMessage.CONNECT, callback);
|
this.onMessage(EventMessage.CONNECT, callback);
|
||||||
@ -449,9 +456,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
|
public onServerDisconnected(callback: () => void): void {
|
||||||
this.socket.addEventListener('close', (event) => {
|
this.socket.addEventListener('close', (event) => {
|
||||||
if (this.closed === true) {
|
if (this.closed === true || connectionManager.unloading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
||||||
@ -459,7 +466,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
// Normal closure case
|
// Normal closure case
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback(event);
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
front/src/Connexion/WorldFullMessageStream.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
class WorldFullMessageStream {
|
||||||
|
|
||||||
|
private _stream:Subject<void> = new Subject();
|
||||||
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
|
onMessage() {
|
||||||
|
this._stream.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const worldFullMessageStream = new WorldFullMessageStream();
|
14
front/src/Connexion/WorldFullWarningStream.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
class WorldFullWarningStream {
|
||||||
|
|
||||||
|
private _stream:Subject<void> = new Subject();
|
||||||
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
|
onMessage() {
|
||||||
|
this._stream.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const worldFullWarningStream = new WorldFullWarningStream();
|
@ -1,8 +1,9 @@
|
|||||||
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 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");
|
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
|
||||||
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
|
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
|
||||||
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost");
|
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
||||||
|
const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost";
|
||||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||||
const TURN_USER: string = process.env.TURN_USER || '';
|
const TURN_USER: string = process.env.TURN_USER || '';
|
||||||
@ -17,7 +18,7 @@ const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new
|
|||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
START_ROOM_URL,
|
START_ROOM_URL,
|
||||||
API_URL,
|
PUSHER_URL,
|
||||||
UPLOADER_URL,
|
UPLOADER_URL,
|
||||||
ADMIN_URL,
|
ADMIN_URL,
|
||||||
RESOLUTION,
|
RESOLUTION,
|
||||||
|
14
front/src/Phaser/Components/WarningContainer.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
export const warningContainerKey = 'warningContainer';
|
||||||
|
export const warningContainerHtml = 'resources/html/warningContainer.html';
|
||||||
|
|
||||||
|
export class WarningContainer extends Phaser.GameObjects.DOMElement {
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene) {
|
||||||
|
super(scene, 100, 0);
|
||||||
|
this.setOrigin(0, 0);
|
||||||
|
this.createFromCache(warningContainerKey);
|
||||||
|
this.scene.add.existing(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -79,7 +79,10 @@ export class GameManager {
|
|||||||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
||||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
||||||
scenePlugin.launch(MenuSceneName);
|
scenePlugin.launch(MenuSceneName);
|
||||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
|
||||||
|
if (!localUserStore.getHelpCameraSettingsShown()) {
|
||||||
|
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public gameSceneIsCreated(scene: GameScene) {
|
public gameSceneIsCreated(scene: GameScene) {
|
||||||
|
@ -29,7 +29,9 @@ import {
|
|||||||
ON_ACTION_TRIGGER_BUTTON,
|
ON_ACTION_TRIGGER_BUTTON,
|
||||||
TRIGGER_JITSI_PROPERTIES,
|
TRIGGER_JITSI_PROPERTIES,
|
||||||
TRIGGER_WEBSITE_PROPERTIES,
|
TRIGGER_WEBSITE_PROPERTIES,
|
||||||
WEBSITE_MESSAGE_PROPERTIES
|
WEBSITE_MESSAGE_PROPERTIES,
|
||||||
|
AUDIO_VOLUME_PROPERTY,
|
||||||
|
AUDIO_LOOP_PROPERTY
|
||||||
} from "../../WebRtc/LayoutManager";
|
} from "../../WebRtc/LayoutManager";
|
||||||
import {GameMap} from "./GameMap";
|
import {GameMap} from "./GameMap";
|
||||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||||
@ -39,7 +41,7 @@ import {ActionableItem} from "../Items/ActionableItem";
|
|||||||
import {UserInputManager} from "../UserInput/UserInputManager";
|
import {UserInputManager} from "../UserInput/UserInputManager";
|
||||||
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||||
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
||||||
import {connectionManager, ConnexionMessageEvent, ConnexionMessageEventTypes} from "../../Connexion/ConnectionManager";
|
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
import {RoomConnection} from "../../Connexion/RoomConnection";
|
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||||
import {userMessageManager} from "../../Administration/UserMessageManager";
|
import {userMessageManager} from "../../Administration/UserMessageManager";
|
||||||
@ -57,12 +59,16 @@ import {TextureError} from "../../Exception/TextureError";
|
|||||||
import {addLoader} from "../Components/Loader";
|
import {addLoader} from "../Components/Loader";
|
||||||
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
|
import {iframeListener} from "../../Api/IframeListener";
|
||||||
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
import Texture = Phaser.Textures.Texture;
|
import Texture = Phaser.Textures.Texture;
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||||
import GameObject = Phaser.GameObjects.GameObject;
|
import GameObject = Phaser.GameObjects.GameObject;
|
||||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||||
|
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||||
import {Subscription} from "rxjs";
|
import {Subscription} from "rxjs";
|
||||||
|
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface|null,
|
initPosition: PointInterface|null,
|
||||||
@ -154,6 +160,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
private playerName!: string;
|
private playerName!: string;
|
||||||
private characterLayers!: string[];
|
private characterLayers!: string[];
|
||||||
private messageSubscription: Subscription|null = null;
|
private messageSubscription: Subscription|null = null;
|
||||||
|
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
|
||||||
|
|
||||||
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
||||||
super({
|
super({
|
||||||
@ -187,6 +194,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
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}) => {
|
||||||
|
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
||||||
|
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:')) {
|
||||||
|
this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://');
|
||||||
|
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
||||||
|
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||||
|
this.onMapLoad(data);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.scene.start(ErrorSceneName, {
|
this.scene.start(ErrorSceneName, {
|
||||||
title: 'Network error',
|
title: 'Network error',
|
||||||
subTitle: 'An error occurred while loading resource:',
|
subTitle: 'An error occurred while loading resource:',
|
||||||
@ -260,7 +276,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error('Unsupported object type: "'+ itemType +'"');
|
continue;
|
||||||
|
//throw new Error('Unsupported object type: "'+ itemType +'"');
|
||||||
}
|
}
|
||||||
|
|
||||||
itemFactory.preload(this.load);
|
itemFactory.preload(this.load);
|
||||||
@ -286,6 +303,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now, let's load the script, if any
|
||||||
|
const scripts = this.getScriptUrls(this.mapFile);
|
||||||
|
for (const script of scripts) {
|
||||||
|
iframeListener.registerScript(script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//hook initialisation
|
//hook initialisation
|
||||||
@ -304,7 +327,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
urlManager.pushRoomIdToUrl(this.room);
|
urlManager.pushRoomIdToUrl(this.room);
|
||||||
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||||
|
|
||||||
this.messageSubscription = connectionManager._connexionMessageStream.subscribe((event) => this.onConnexionMessage(event))
|
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
|
||||||
|
|
||||||
const playerName = gameManager.getPlayerName();
|
const playerName = gameManager.getPlayerName();
|
||||||
if (!playerName) {
|
if (!playerName) {
|
||||||
@ -370,19 +393,21 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.initCirclesCanvas();
|
this.initCirclesCanvas();
|
||||||
|
|
||||||
// Let's pause the scene if the connection is not established yet
|
// Let's pause the scene if the connection is not established yet
|
||||||
if (this.isReconnecting) {
|
if (!this.room.isDisconnected()) {
|
||||||
setTimeout(() => {
|
if (this.isReconnecting) {
|
||||||
this.scene.sleep();
|
setTimeout(() => {
|
||||||
this.scene.launch(ReconnectingSceneName);
|
|
||||||
}, 0);
|
|
||||||
} else if (this.connection === undefined) {
|
|
||||||
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.connection === undefined) {
|
|
||||||
this.scene.sleep();
|
this.scene.sleep();
|
||||||
this.scene.launch(ReconnectingSceneName);
|
this.scene.launch(ReconnectingSceneName);
|
||||||
}
|
}, 0);
|
||||||
}, 1000);
|
} else if (this.connection === undefined) {
|
||||||
|
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.connection === undefined) {
|
||||||
|
this.scene.sleep();
|
||||||
|
this.scene.launch(ReconnectingSceneName);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.createPromiseResolve();
|
this.createPromiseResolve();
|
||||||
@ -407,7 +432,18 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
// From now, this game scene will be notified of reposition events
|
// From now, this game scene will be notified of reposition events
|
||||||
layoutManager.setListener(this);
|
layoutManager.setListener(this);
|
||||||
this.triggerOnMapLayerPropertyChange();
|
this.triggerOnMapLayerPropertyChange();
|
||||||
|
this.listenToIframeEvents();
|
||||||
|
|
||||||
|
|
||||||
|
if (!this.room.isDisconnected()) {
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connection to Pusher.
|
||||||
|
*/
|
||||||
|
private connect(): void {
|
||||||
const camera = this.cameras.main;
|
const camera = this.cameras.main;
|
||||||
|
|
||||||
connectionManager.connectToRoomSocket(
|
connectionManager.connectToRoomSocket(
|
||||||
@ -455,16 +491,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
||||||
audioManager.decreaseVolume();
|
|
||||||
this.shareGroupPosition(groupPositionMessage);
|
this.shareGroupPosition(groupPositionMessage);
|
||||||
this.openChatIcon.setVisible(true);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.onGroupDeleted((groupId: number) => {
|
this.connection.onGroupDeleted((groupId: number) => {
|
||||||
audioManager.restoreVolume();
|
|
||||||
try {
|
try {
|
||||||
this.deleteGroup(groupId);
|
this.deleteGroup(groupId);
|
||||||
this.openChatIcon.setVisible(false);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@ -472,9 +504,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
this.connection.onServerDisconnected(() => {
|
this.connection.onServerDisconnected(() => {
|
||||||
console.log('Player disconnected from server. Reloading scene.');
|
console.log('Player disconnected from server. Reloading scene.');
|
||||||
|
this.cleanupClosingScene();
|
||||||
this.simplePeer.closeAllConnections();
|
|
||||||
this.simplePeer.unregister();
|
|
||||||
|
|
||||||
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
||||||
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
||||||
@ -517,11 +547,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
onConnect(user: UserSimplePeerInterface) {
|
onConnect(user: UserSimplePeerInterface) {
|
||||||
self.presentationModeSprite.setVisible(true);
|
self.presentationModeSprite.setVisible(true);
|
||||||
self.chatModeSprite.setVisible(true);
|
self.chatModeSprite.setVisible(true);
|
||||||
|
self.openChatIcon.setVisible(true);
|
||||||
|
audioManager.decreaseVolume();
|
||||||
},
|
},
|
||||||
onDisconnect(userId: number) {
|
onDisconnect(userId: number) {
|
||||||
if (self.simplePeer.getNbConnections() === 0) {
|
if (self.simplePeer.getNbConnections() === 0) {
|
||||||
self.presentationModeSprite.setVisible(false);
|
self.presentationModeSprite.setVisible(false);
|
||||||
self.chatModeSprite.setVisible(false);
|
self.chatModeSprite.setVisible(false);
|
||||||
|
self.openChatIcon.setVisible(false);
|
||||||
|
audioManager.restoreVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -547,7 +581,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//todo: into dedicated classes
|
//todo: into dedicated classes
|
||||||
private initCirclesCanvas(): void {
|
private initCirclesCanvas(): void {
|
||||||
// Let's generate the circle for the group delimiter
|
// Let's generate the circle for the group delimiter
|
||||||
@ -576,7 +609,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
const contextRed = this.circleRedTexture.context;
|
const contextRed = this.circleRedTexture.context;
|
||||||
contextRed.beginPath();
|
contextRed.beginPath();
|
||||||
contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false);
|
contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false);
|
||||||
// context.lineWidth = 5;
|
//context.lineWidth = 5;
|
||||||
contextRed.strokeStyle = '#ff0000';
|
contextRed.strokeStyle = '#ff0000';
|
||||||
contextRed.stroke();
|
contextRed.stroke();
|
||||||
this.circleRedTexture.refresh();
|
this.circleRedTexture.refresh();
|
||||||
@ -605,7 +638,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
}else{
|
}else{
|
||||||
const openWebsiteFunction = () => {
|
const openWebsiteFunction = () => {
|
||||||
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined);
|
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined);
|
||||||
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -662,13 +695,111 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.connection.setSilent(true);
|
this.connection.setSilent(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
|
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
|
||||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
|
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined;
|
||||||
|
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined;
|
||||||
|
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
|
||||||
|
});
|
||||||
|
// TODO: This legacy property should be removed at some point
|
||||||
|
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
|
||||||
|
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
|
this.gameMap.onPropertyChange('zone', (newValue, oldValue) => {
|
||||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
|
if (newValue === undefined || newValue === false || newValue === '') {
|
||||||
|
iframeListener.sendLeaveEvent(oldValue as string);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
iframeListener.sendEnterEvent(newValue as string);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private listenToIframeEvents(): void {
|
||||||
|
iframeListener.openPopupStream.subscribe((openPopupEvent) => {
|
||||||
|
|
||||||
|
let objectLayerSquare : ITiledMapObject;
|
||||||
|
const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject);
|
||||||
|
if (targetObjectData !== undefined){
|
||||||
|
objectLayerSquare = targetObjectData;
|
||||||
|
} else {
|
||||||
|
console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message);
|
||||||
|
let html = `<div id="container"><div class="nes-container with-title is-centered">
|
||||||
|
${escapedMessage}
|
||||||
|
</div> </div>`;
|
||||||
|
const buttonContainer = `<div class="buttonContainer"</div>`;
|
||||||
|
html += buttonContainer;
|
||||||
|
let id = 0;
|
||||||
|
for (const button of openPopupEvent.buttons) {
|
||||||
|
html += `<button type="button" class="nes-btn is-${HtmlUtils.escapeHtml(button.className ?? '')}" id="popup-${openPopupEvent.popupId}-${id}">${HtmlUtils.escapeHtml(button.label)}</button>`;
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
const domElement = this.add.dom(objectLayerSquare.x + objectLayerSquare.width/2 ,
|
||||||
|
objectLayerSquare.y + objectLayerSquare.height/2).createFromHTML(html);
|
||||||
|
|
||||||
|
const container : HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement;
|
||||||
|
container.style.width = objectLayerSquare.width + "px";
|
||||||
|
domElement.scale = 0;
|
||||||
|
domElement.setClassName('popUpElement');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
id = 0;
|
||||||
|
for (const button of openPopupEvent.buttons) {
|
||||||
|
const button = HtmlUtils.getElementByIdOrFail<HTMLButtonElement>(`popup-${openPopupEvent.popupId}-${id}`);
|
||||||
|
const btnId = id;
|
||||||
|
button.onclick = () => {
|
||||||
|
iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId);
|
||||||
|
}
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets : domElement ,
|
||||||
|
scale : 1,
|
||||||
|
ease : "EaseOut",
|
||||||
|
duration : 400,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.popUpElements.set(openPopupEvent.popupId, domElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
iframeListener.closePopupStream.subscribe((closePopupEvent) => {
|
||||||
|
const popUpElement = this.popUpElements.get(closePopupEvent.popupId);
|
||||||
|
if (popUpElement === undefined) {
|
||||||
|
console.error('Could not close popup with ID ', closePopupEvent.popupId,'. Maybe it has already been closed?');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets : popUpElement ,
|
||||||
|
scale : 0,
|
||||||
|
ease : "EaseOut",
|
||||||
|
duration : 400,
|
||||||
|
onComplete : () => {
|
||||||
|
popUpElement?.destroy();
|
||||||
|
this.popUpElements.delete(closePopupEvent.popupId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
iframeListener.disablePlayerControlStream.subscribe(()=>{
|
||||||
|
this.userInputManager.disableControls();
|
||||||
|
})
|
||||||
|
iframeListener.enablePlayerControlStream.subscribe(()=>{
|
||||||
|
this.userInputManager.restoreControls();
|
||||||
|
})
|
||||||
|
let scriptedBubbleSprite : Sprite;
|
||||||
|
iframeListener.displayBubbleStream.subscribe(()=>{
|
||||||
|
scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white');
|
||||||
|
scriptedBubbleSprite.setDisplayOrigin(48, 48);
|
||||||
|
this.add.existing(scriptedBubbleSprite);
|
||||||
|
})
|
||||||
|
iframeListener.removeBubbleStream.subscribe(()=>{
|
||||||
|
scriptedBubbleSprite.destroy();
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,10 +831,17 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
public cleanupClosingScene(): void {
|
public cleanupClosingScene(): void {
|
||||||
// stop playing audio, close any open website, stop any open Jitsi
|
// stop playing audio, close any open website, stop any open Jitsi
|
||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
|
// Stop the script, if any
|
||||||
|
const scripts = this.getScriptUrls(this.mapFile);
|
||||||
|
for (const script of scripts) {
|
||||||
|
iframeListener.unregisterScript(script);
|
||||||
|
}
|
||||||
|
|
||||||
this.stopJitsi();
|
this.stopJitsi();
|
||||||
audioManager.unloadAudio();
|
audioManager.unloadAudio();
|
||||||
// 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.
|
||||||
this.connection?.closeConnection();
|
this.connection?.closeConnection();
|
||||||
|
this.simplePeer.closeAllConnections();
|
||||||
this.simplePeer?.unregister();
|
this.simplePeer?.unregister();
|
||||||
this.messageSubscription?.unsubscribe();
|
this.messageSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
@ -782,8 +920,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
return this.getProperty(layer, "startLayer") == true;
|
return this.getProperty(layer, "startLayer") == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getProperty(layer: ITiledMapLayer, name: string): string|boolean|number|undefined {
|
private getScriptUrls(map: ITiledMap): string[] {
|
||||||
const properties = layer.properties;
|
return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined {
|
||||||
|
const properties: ITiledMapLayerProperty[] = layer.properties;
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -794,6 +936,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
return obj.value;
|
return obj.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] {
|
||||||
|
const properties: ITiledMapLayerProperty[] = layer.properties;
|
||||||
|
if (!properties) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return properties.filter((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase()).map((property) => property.value);
|
||||||
|
}
|
||||||
|
|
||||||
//todo: push that into the gameManager
|
//todo: push that into the gameManager
|
||||||
private async loadNextGame(exitSceneIdentifier: string){
|
private async loadNextGame(exitSceneIdentifier: string){
|
||||||
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
||||||
@ -814,7 +964,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
const y = Math.floor(key / layer.width);
|
const y = Math.floor(key / layer.width);
|
||||||
const x = key % layer.width;
|
const x = key % layer.width;
|
||||||
|
|
||||||
possibleStartPositions.push({x: x*32, y: y*32});
|
possibleStartPositions.push({x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth});
|
||||||
});
|
});
|
||||||
// Get a value at random amongst allowed values
|
// Get a value at random amongst allowed values
|
||||||
if (possibleStartPositions.length === 0) {
|
if (possibleStartPositions.length === 0) {
|
||||||
@ -1173,7 +1323,19 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
bottom: camera.scrollY + camera.height,
|
bottom: camera.scrollY + camera.height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
private getObjectLayerData(objectName : string) : ITiledMapObject| undefined{
|
||||||
|
for (const layer of this.mapFile.layers) {
|
||||||
|
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||||
|
for (const object of layer.objects) {
|
||||||
|
if (object.name === objectName) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
}
|
||||||
private reposition(): void {
|
private reposition(): void {
|
||||||
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||||
this.chatModeSprite.setY(this.game.renderer.height - 2);
|
this.chatModeSprite.setY(this.game.renderer.height - 2);
|
||||||
@ -1227,10 +1389,10 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: into onConnexionMessage
|
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||||
private bannedUser(){
|
private bannedUser(){
|
||||||
this.cleanupClosingScene();
|
this.cleanupClosingScene();
|
||||||
this.userInputManager.clearAllKeys();
|
this.userInputManager.disableControls();
|
||||||
this.scene.start(ErrorSceneName, {
|
this.scene.start(ErrorSceneName, {
|
||||||
title: 'Banned',
|
title: 'Banned',
|
||||||
subTitle: 'You were banned from WorkAdventure',
|
subTitle: 'You were banned from WorkAdventure',
|
||||||
@ -1238,16 +1400,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onConnexionMessage(event: ConnexionMessageEvent) {
|
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||||
if (event.type === ConnexionMessageEventTypes.worldFull) {
|
private showWorldFullError(): void {
|
||||||
this.cleanupClosingScene();
|
this.cleanupClosingScene();
|
||||||
this.scene.stop(ReconnectingSceneName);
|
this.scene.stop(ReconnectingSceneName);
|
||||||
this.userInputManager.clearAllKeys();
|
this.userInputManager.disableControls();
|
||||||
this.scene.start(ErrorSceneName, {
|
this.scene.start(ErrorSceneName, {
|
||||||
title: 'Connection rejected',
|
title: 'Connection rejected',
|
||||||
subTitle: 'The world you are trying to join is full. Try again later.',
|
subTitle: 'The world you are trying to join is full. Try again later.',
|
||||||
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,26 +61,22 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
|
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
|
||||||
|
|
||||||
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
|
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||||
this.arrowRight.setOrigin(0.5, 0.5);
|
|
||||||
this.arrowRight.setVisible(false);
|
this.arrowRight.setVisible(false);
|
||||||
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
|
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
|
||||||
this.add.existing(this.arrowRight);
|
this.add.existing(this.arrowRight);
|
||||||
|
|
||||||
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
|
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||||
this.arrowLeft.setOrigin(0.5, 0.5);
|
|
||||||
this.arrowLeft.setVisible(false);
|
this.arrowLeft.setVisible(false);
|
||||||
this.arrowLeft.flipX = true;
|
this.arrowLeft.flipX = true;
|
||||||
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
|
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
|
||||||
this.add.existing(this.arrowLeft);
|
this.add.existing(this.arrowLeft);
|
||||||
|
|
||||||
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
|
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||||
this.arrowUp.setOrigin(0.5, 0.5);
|
|
||||||
this.arrowUp.setVisible(false);
|
this.arrowUp.setVisible(false);
|
||||||
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
|
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
|
||||||
this.add.existing(this.arrowUp);
|
this.add.existing(this.arrowUp);
|
||||||
|
|
||||||
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
|
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||||
this.arrowDown.setOrigin(0.5, 0.5);
|
|
||||||
this.arrowDown.setVisible(false);
|
this.arrowDown.setVisible(false);
|
||||||
this.arrowDown.flipY = true;
|
this.arrowDown.flipY = true;
|
||||||
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
||||||
@ -164,8 +160,6 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private updateWebCamName(): void {
|
private updateWebCamName(): void {
|
||||||
if (this.camerasList.length > 1) {
|
if (this.camerasList.length > 1) {
|
||||||
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
|
||||||
|
|
||||||
let label = this.camerasList[this.cameraSelected].label;
|
let label = this.camerasList[this.cameraSelected].label;
|
||||||
// remove text in parenthesis
|
// remove text in parenthesis
|
||||||
label = label.replace(/\([^()]*\)/g, '').trim();
|
label = label.replace(/\([^()]*\)/g, '').trim();
|
||||||
@ -173,17 +167,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||||
this.cameraNameField.text = label;
|
this.cameraNameField.text = label;
|
||||||
|
|
||||||
if (this.cameraSelected < this.camerasList.length - 1) {
|
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
|
||||||
this.arrowRight.setVisible(true);
|
this.arrowLeft.setVisible(this.cameraSelected > 0);
|
||||||
} else {
|
|
||||||
this.arrowRight.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.cameraSelected > 0) {
|
|
||||||
this.arrowLeft.setVisible(true);
|
|
||||||
} else {
|
|
||||||
this.arrowLeft.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this.microphonesList.length > 1) {
|
if (this.microphonesList.length > 1) {
|
||||||
let label = this.microphonesList[this.microphoneSelected].label;
|
let label = this.microphonesList[this.microphoneSelected].label;
|
||||||
@ -194,17 +179,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
|
|
||||||
this.microphoneNameField.text = label;
|
this.microphoneNameField.text = label;
|
||||||
|
|
||||||
if (this.microphoneSelected < this.microphonesList.length - 1) {
|
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
|
||||||
this.arrowDown.setVisible(true);
|
this.arrowUp.setVisible(this.microphoneSelected > 0);
|
||||||
} else {
|
|
||||||
this.arrowDown.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.microphoneSelected > 0) {
|
|
||||||
this.arrowUp.setVisible(true);
|
|
||||||
} else {
|
|
||||||
this.arrowUp.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
this.reposition();
|
this.reposition();
|
||||||
|
@ -14,7 +14,7 @@ export interface ITiledMap {
|
|||||||
* Map orientation (orthogonal)
|
* Map orientation (orthogonal)
|
||||||
*/
|
*/
|
||||||
orientation: string;
|
orientation: string;
|
||||||
properties: {[key: string]: string};
|
properties: ITiledMapLayerProperty[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render order (right-down)
|
* Render order (right-down)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
|
|
||||||
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
||||||
const helpCameraSettings = 'helpCameraSettings';
|
const helpCameraSettings = 'helpCameraSettings';
|
||||||
@ -19,6 +20,7 @@ export class HelpCameraSettingsScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(){
|
create(){
|
||||||
|
localUserStore.setHelpCameraSettingsShown();
|
||||||
this.createHelpCameraSettings();
|
this.createHelpCameraSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import {mediaManager} from "../../WebRtc/MediaManager";
|
|||||||
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
|
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
|
||||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||||
|
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
||||||
|
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
||||||
|
|
||||||
export const MenuSceneName = 'MenuScene';
|
export const MenuSceneName = 'MenuScene';
|
||||||
const gameMenuKey = 'gameMenu';
|
const gameMenuKey = 'gameMenu';
|
||||||
@ -30,6 +32,8 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
private gameQualityValue: number;
|
private gameQualityValue: number;
|
||||||
private videoQualityValue: number;
|
private videoQualityValue: number;
|
||||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
private menuButton!: Phaser.GameObjects.DOMElement;
|
||||||
|
private warningContainer: WarningContainer | null = null;
|
||||||
|
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({key: MenuSceneName});
|
super({key: MenuSceneName});
|
||||||
@ -44,6 +48,7 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||||
this.load.html(gameShare, 'resources/html/gameShare.html');
|
this.load.html(gameShare, 'resources/html/gameShare.html');
|
||||||
this.load.html(gameReportKey, gameReportRessource);
|
this.load.html(gameReportKey, gameReportRessource);
|
||||||
|
this.load.html(warningContainerKey, warningContainerHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
@ -85,6 +90,8 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
|
|
||||||
this.menuElement.addListener('click');
|
this.menuElement.addListener('click');
|
||||||
this.menuElement.on('click', this.onMenuClick.bind(this));
|
this.menuElement.on('click', this.onMenuClick.bind(this));
|
||||||
|
|
||||||
|
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo put this method in a parent menuElement class
|
//todo put this method in a parent menuElement class
|
||||||
@ -122,6 +129,21 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showWorldCapacityWarning() {
|
||||||
|
if (!this.warningContainer) {
|
||||||
|
this.warningContainer = new WarningContainer(this);
|
||||||
|
}
|
||||||
|
if (this.warningContainerTimeout) {
|
||||||
|
clearTimeout(this.warningContainerTimeout);
|
||||||
|
}
|
||||||
|
this.warningContainerTimeout = setTimeout(() => {
|
||||||
|
this.warningContainer?.destroy();
|
||||||
|
this.warningContainer = null
|
||||||
|
this.warningContainerTimeout = null
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private closeSideMenu(): void {
|
private closeSideMenu(): void {
|
||||||
if (!this.sideMenuOpened) return;
|
if (!this.sideMenuOpened) return;
|
||||||
this.sideMenuOpened = false;
|
this.sideMenuOpened = false;
|
||||||
|
@ -61,7 +61,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
|||||||
|
|
||||||
this.opened = true;
|
this.opened = true;
|
||||||
|
|
||||||
gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys();
|
gameManager.getCurrentGameScene(this.scene).userInputManager.disableControls();
|
||||||
|
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: this,
|
targets: this,
|
||||||
|
@ -31,10 +31,11 @@ export class ActiveEventList {
|
|||||||
export class UserInputManager {
|
export class UserInputManager {
|
||||||
private KeysCode!: UserInputManagerDatum[];
|
private KeysCode!: UserInputManagerDatum[];
|
||||||
private Scene: GameScene;
|
private Scene: GameScene;
|
||||||
|
private isInputDisabled : boolean;
|
||||||
constructor(Scene : GameScene) {
|
constructor(Scene : GameScene) {
|
||||||
this.Scene = Scene;
|
this.Scene = Scene;
|
||||||
this.initKeyBoardEvent();
|
this.initKeyBoardEvent();
|
||||||
|
this.isInputDisabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
initKeyBoardEvent(){
|
initKeyBoardEvent(){
|
||||||
@ -63,16 +64,25 @@ export class UserInputManager {
|
|||||||
this.Scene.input.keyboard.removeAllListeners();
|
this.Scene.input.keyboard.removeAllListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAllKeys(){
|
disableControls(){
|
||||||
this.Scene.input.keyboard.removeAllKeys();
|
this.Scene.input.keyboard.removeAllKeys();
|
||||||
|
this.isInputDisabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreControls(){
|
||||||
|
this.initKeyBoardEvent();
|
||||||
|
this.isInputDisabled = false;
|
||||||
|
}
|
||||||
getEventListForGameTick(): ActiveEventList {
|
getEventListForGameTick(): ActiveEventList {
|
||||||
const eventsMap = new ActiveEventList();
|
const eventsMap = new ActiveEventList();
|
||||||
|
if (this.isInputDisabled) {
|
||||||
|
return eventsMap;
|
||||||
|
}
|
||||||
this.KeysCode.forEach(d => {
|
this.KeysCode.forEach(d => {
|
||||||
if (d. keyInstance.isDown) {
|
if (d. keyInstance.isDown) {
|
||||||
eventsMap.set(d.event, true);
|
eventsMap.set(d.event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return eventsMap;
|
return eventsMap;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,8 @@ class UrlManager {
|
|||||||
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);
|
const search = room.search.toString();
|
||||||
|
history.pushState({}, 'WorkAdventure', room.id+(search?'?'+search:'')+hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStartLayerNameFromUrl(): string|null {
|
public getStartLayerNameFromUrl(): string|null {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
import {isUndefined} from "generic-type-guard";
|
import {isUndefined} from "generic-type-guard";
|
||||||
|
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||||
|
|
||||||
enum audioStates {
|
enum audioStates {
|
||||||
closed = 0,
|
closed = 0,
|
||||||
@ -9,6 +10,8 @@ enum audioStates {
|
|||||||
|
|
||||||
const audioPlayerDivId = "audioplayer";
|
const audioPlayerDivId = "audioplayer";
|
||||||
const audioPlayerCtrlId = "audioplayerctrl";
|
const audioPlayerCtrlId = "audioplayerctrl";
|
||||||
|
const audioPlayerVolId = "audioplayer_volume";
|
||||||
|
const audioPlayerMuteId = "audioplayer_volume_icon_playing";
|
||||||
const animationTime = 500;
|
const animationTime = 500;
|
||||||
|
|
||||||
class AudioManager {
|
class AudioManager {
|
||||||
@ -17,6 +20,8 @@ class AudioManager {
|
|||||||
private audioPlayerDiv: HTMLDivElement;
|
private audioPlayerDiv: HTMLDivElement;
|
||||||
private audioPlayerCtrl: HTMLDivElement;
|
private audioPlayerCtrl: HTMLDivElement;
|
||||||
private audioPlayerElem: HTMLAudioElement | undefined;
|
private audioPlayerElem: HTMLAudioElement | undefined;
|
||||||
|
private audioPlayerVol: HTMLInputElement;
|
||||||
|
private audioPlayerMute: HTMLInputElement;
|
||||||
|
|
||||||
private volume = 1;
|
private volume = 1;
|
||||||
private muted = false;
|
private muted = false;
|
||||||
@ -26,19 +31,19 @@ class AudioManager {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
|
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
|
||||||
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
|
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
|
||||||
|
this.audioPlayerVol = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerVolId);
|
||||||
|
this.audioPlayerMute = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerMuteId);
|
||||||
|
|
||||||
const storedVolume = localStorage.getItem('volume')
|
this.volume = localUserStore.getAudioPlayerVolume();
|
||||||
if (storedVolume === null) {
|
this.audioPlayerVol.value = '' + this.volume;
|
||||||
this.setVolume(1);
|
|
||||||
} else {
|
this.muted = localUserStore.getAudioPlayerMuted();
|
||||||
this.volume = parseFloat(storedVolume);
|
if (this.muted) {
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = storedVolume;
|
this.audioPlayerMute.classList.add('muted');
|
||||||
}
|
}
|
||||||
|
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public playAudio(url: string|number|boolean, mapDirUrl: string, loop=false): void {
|
public playAudio(url: string|number|boolean, mapDirUrl: string, volume: number|undefined, loop=false): void {
|
||||||
const audioPath = url as string;
|
const audioPath = url as string;
|
||||||
let realAudioPath = '';
|
let realAudioPath = '';
|
||||||
|
|
||||||
@ -50,7 +55,7 @@ class AudioManager {
|
|||||||
realAudioPath = mapDirUrl + '/' + url;
|
realAudioPath = mapDirUrl + '/' + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadAudio(realAudioPath);
|
this.loadAudio(realAudioPath, volume);
|
||||||
|
|
||||||
if (loop) {
|
if (loop) {
|
||||||
this.loop();
|
this.loop();
|
||||||
@ -75,26 +80,29 @@ class AudioManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private changeVolume(talking = false): void {
|
private changeVolume(talking = false): void {
|
||||||
if (!isUndefined(this.audioPlayerElem)) {
|
if (isUndefined(this.audioPlayerElem)) {
|
||||||
this.audioPlayerElem.volume = this.naturalVolume(talking && this.decreaseWhileTalking);
|
return;
|
||||||
this.audioPlayerElem.muted = this.muted;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private naturalVolume(makeSofter: boolean = false): number {
|
const reduceVolume = talking && this.decreaseWhileTalking;
|
||||||
const volume = this.volume
|
if (reduceVolume && !this.volumeReduced) {
|
||||||
const retVol = makeSofter && !this.volumeReduced ? Math.pow(volume * 0.5, 3) : volume
|
this.volume *= 0.5;
|
||||||
this.volumeReduced = makeSofter
|
} else if (!reduceVolume && this.volumeReduced) {
|
||||||
return retVol;
|
this.volume *= 2.0;
|
||||||
|
}
|
||||||
|
this.volumeReduced = reduceVolume;
|
||||||
|
|
||||||
|
this.audioPlayerElem.volume = this.volume;
|
||||||
|
this.audioPlayerVol.value = '' + this.volume;
|
||||||
|
this.audioPlayerElem.muted = this.muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setVolume(volume: number): void {
|
private setVolume(volume: number): void {
|
||||||
this.volume = volume;
|
this.volume = volume;
|
||||||
localStorage.setItem('volume', '' + volume);
|
localUserStore.setAudioPlayerVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadAudio(url: string, volume: number|undefined): void {
|
||||||
private loadAudio(url: string): void {
|
|
||||||
this.load();
|
this.load();
|
||||||
|
|
||||||
/* Solution 1, remove whole audio player */
|
/* Solution 1, remove whole audio player */
|
||||||
@ -112,23 +120,24 @@ class AudioManager {
|
|||||||
this.audioPlayerElem.append(srcElem);
|
this.audioPlayerElem.append(srcElem);
|
||||||
|
|
||||||
this.audioPlayerDiv.append(this.audioPlayerElem);
|
this.audioPlayerDiv.append(this.audioPlayerElem);
|
||||||
|
this.volume = volume ? Math.min(volume, this.volume) : this.volume;
|
||||||
this.changeVolume();
|
this.changeVolume();
|
||||||
this.audioPlayerElem.play();
|
this.audioPlayerElem.play();
|
||||||
|
|
||||||
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
|
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
|
||||||
muteElem.onclick = (ev: Event)=> {
|
muteElem.onclick = (ev: Event) => {
|
||||||
this.muted = !this.muted;
|
this.muted = !this.muted;
|
||||||
this.changeVolume();
|
this.changeVolume();
|
||||||
|
localUserStore.setAudioPlayerMuted(this.muted);
|
||||||
|
|
||||||
if (this.muted) {
|
if (this.muted) {
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.add('muted');
|
this.audioPlayerMute.classList.add('muted');
|
||||||
} else {
|
} else {
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.remove('muted');
|
this.audioPlayerMute.classList.remove('muted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const volumeElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume');
|
this.audioPlayerVol.oninput = (ev: Event)=> {
|
||||||
volumeElem.oninput = (ev: Event)=> {
|
|
||||||
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
|
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
|
||||||
this.changeVolume();
|
this.changeVolume();
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
import {Subject} from "rxjs";
|
import {Subject} from "rxjs";
|
||||||
|
import {iframeListener} from "../Api/IframeListener";
|
||||||
|
|
||||||
enum iframeStates {
|
enum iframeStates {
|
||||||
closed = 1,
|
closed = 1,
|
||||||
@ -53,7 +54,7 @@ class CoWebsiteManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isFullScreen(): boolean {
|
get isFullScreen(): boolean {
|
||||||
return this.verticalMode ? this.height === this.cowebsiteDiv.clientHeight : this.width === this.cowebsiteDiv.clientWidth
|
return this.verticalMode ? this.height === window.innerHeight : this.width === window.innerWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -73,7 +74,7 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
private initResizeListeners() {
|
private initResizeListeners() {
|
||||||
const movecallback = (event:MouseEvent) => {
|
const movecallback = (event:MouseEvent) => {
|
||||||
this.verticalMode ? this.height -= event.movementY : this.width -= event.movementX;
|
this.verticalMode ? this.height -= event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
|
||||||
this.fire();
|
this.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +93,12 @@ class CoWebsiteManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDevicePixelRatio(): number {
|
||||||
|
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
|
||||||
|
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
|
||||||
|
return window.navigator.userAgent.includes('Firefox') ? 1 : window.devicePixelRatio;
|
||||||
|
}
|
||||||
|
|
||||||
private close(): void {
|
private close(): void {
|
||||||
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
||||||
this.cowebsiteDiv.classList.add('hidden');
|
this.cowebsiteDiv.classList.add('hidden');
|
||||||
@ -120,7 +127,7 @@ class CoWebsiteManager {
|
|||||||
return iframe;
|
return iframe;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadCoWebsite(url: string, base: string, allowPolicy?: string): void {
|
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteMainDom.innerHTML = ``;
|
this.cowebsiteMainDom.innerHTML = ``;
|
||||||
|
|
||||||
@ -133,6 +140,9 @@ class CoWebsiteManager {
|
|||||||
const onloadPromise = new Promise((resolve) => {
|
const onloadPromise = new Promise((resolve) => {
|
||||||
iframe.onload = () => resolve();
|
iframe.onload = () => resolve();
|
||||||
});
|
});
|
||||||
|
if (allowApi) {
|
||||||
|
iframeListener.registerIframe(iframe);
|
||||||
|
}
|
||||||
this.cowebsiteMainDom.appendChild(iframe);
|
this.cowebsiteMainDom.appendChild(iframe);
|
||||||
const onTimeoutPromise = new Promise((resolve) => {
|
const onTimeoutPromise = new Promise((resolve) => {
|
||||||
setTimeout(() => resolve(), 2000);
|
setTimeout(() => resolve(), 2000);
|
||||||
@ -164,6 +174,10 @@ class CoWebsiteManager {
|
|||||||
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
|
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
|
||||||
this.close();
|
this.close();
|
||||||
this.fire();
|
this.fire();
|
||||||
|
const iframe = this.cowebsiteDiv.querySelector('iframe');
|
||||||
|
if (iframe) {
|
||||||
|
iframeListener.unregisterIframe(iframe);
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cowebsiteMainDom.innerHTML = ``;
|
this.cowebsiteMainDom.innerHTML = ``;
|
||||||
resolve();
|
resolve();
|
||||||
@ -199,6 +213,7 @@ class CoWebsiteManager {
|
|||||||
private fullscreen(): void {
|
private fullscreen(): void {
|
||||||
if (this.isFullScreen) {
|
if (this.isFullScreen) {
|
||||||
this.resetStyle();
|
this.resetStyle();
|
||||||
|
this.fire();
|
||||||
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
||||||
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
|
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
|
||||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
|
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
|
||||||
|
@ -3,6 +3,7 @@ import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
|
|||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||||
|
import {iframeListener} from "../Api/IframeListener";
|
||||||
|
|
||||||
export type SendMessageCallback = (message:string) => void;
|
export type SendMessageCallback = (message:string) => void;
|
||||||
|
|
||||||
@ -25,6 +26,14 @@ export class DiscussionManager {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||||
this.createDiscussPart(''); //todo: why do we always use empty string?
|
this.createDiscussPart(''); //todo: why do we always use empty string?
|
||||||
|
|
||||||
|
iframeListener.chatStream.subscribe((chatEvent) => {
|
||||||
|
this.addMessage(chatEvent.author, chatEvent.message, false);
|
||||||
|
this.showDiscussion();
|
||||||
|
});
|
||||||
|
this.onSendMessageCallback('iframe_listener', (message) => {
|
||||||
|
iframeListener.sendUserInputChat(message);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private createDiscussPart(name: string) {
|
private createDiscussPart(name: string) {
|
||||||
@ -61,12 +70,12 @@ export class DiscussionManager {
|
|||||||
const inputMessage: HTMLInputElement = document.createElement('input');
|
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||||
inputMessage.onfocus = () => {
|
inputMessage.onfocus = () => {
|
||||||
if(this.userInputManager) {
|
if(this.userInputManager) {
|
||||||
this.userInputManager.clearAllKeys();
|
this.userInputManager.disableControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputMessage.onblur = () => {
|
inputMessage.onblur = () => {
|
||||||
if(this.userInputManager) {
|
if(this.userInputManager) {
|
||||||
this.userInputManager.initKeyBoardEvent();
|
this.userInputManager.restoreControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputMessage.type = "text";
|
inputMessage.type = "text";
|
||||||
|
@ -24,7 +24,7 @@ export class HtmlUtils {
|
|||||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static escapeHtml(html: string): string {
|
public static escapeHtml(html: string): string {
|
||||||
const text = document.createTextNode(html);
|
const text = document.createTextNode(html);
|
||||||
const p = document.createElement('p');
|
const p = document.createElement('p');
|
||||||
p.appendChild(text);
|
p.appendChild(text);
|
||||||
|
@ -31,6 +31,9 @@ export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
|
|||||||
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
|
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
|
||||||
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
|
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
|
||||||
|
|
||||||
|
export const AUDIO_VOLUME_PROPERTY = 'audioVolume';
|
||||||
|
export const AUDIO_LOOP_PROPERTY = 'audioLoop';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is in charge of the video-conference layout.
|
* This class is in charge of the video-conference layout.
|
||||||
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
||||||
|
@ -2,19 +2,14 @@ import {DivImportance, layoutManager} from "./LayoutManager";
|
|||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
|
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
|
|
||||||
let valueVideo = 20;
|
|
||||||
if(localValueVideo){
|
|
||||||
valueVideo = parseInt(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: { ideal: valueVideo },
|
frameRate: { ideal: localUserStore.getVideoQualityValue() },
|
||||||
facingMode: "user",
|
facingMode: "user",
|
||||||
resizeMode: 'crop-and-scale',
|
resizeMode: 'crop-and-scale',
|
||||||
aspectRatio: 1.777777778
|
aspectRatio: 1.777777778
|
||||||
|
@ -63,7 +63,7 @@ export class SimplePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getNbConnections(): number {
|
public getNbConnections(): number {
|
||||||
return this.PeerConnectionArray.size;
|
return this.Users.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,9 +230,6 @@ export class SimplePeer {
|
|||||||
|
|
||||||
this.closeScreenSharingConnection(userId);
|
this.closeScreenSharingConnection(userId);
|
||||||
|
|
||||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
|
||||||
peerConnectionListener.onDisconnect(userId);
|
|
||||||
}
|
|
||||||
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
||||||
if(userIndex < 0){
|
if(userIndex < 0){
|
||||||
throw 'Couln\'t delete user';
|
throw 'Couln\'t delete user';
|
||||||
@ -250,6 +247,10 @@ export class SimplePeer {
|
|||||||
this.PeerScreenSharingConnectionArray.delete(userId);
|
this.PeerScreenSharingConnectionArray.delete(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||||
|
peerConnectionListener.onDisconnect(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
232
front/src/iframe_api.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import {ChatEvent, isChatEvent} from "./Api/Events/ChatEvent";
|
||||||
|
import {isIframeEventWrapper} from "./Api/Events/IframeEvent";
|
||||||
|
import {isUserInputChatEvent, UserInputChatEvent} from "./Api/Events/UserInputChatEvent";
|
||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {EnterLeaveEvent, isEnterLeaveEvent} from "./Api/Events/EnterLeaveEvent";
|
||||||
|
import {OpenPopupEvent} from "./Api/Events/OpenPopupEvent";
|
||||||
|
import {isButtonClickedEvent} from "./Api/Events/ButtonClickedEvent";
|
||||||
|
import {ClosePopupEvent} from "./Api/Events/ClosePopupEvent";
|
||||||
|
import {OpenTabEvent} from "./Api/Events/OpenTabEvent";
|
||||||
|
import {GoToPageEvent} from "./Api/Events/GoToPageEvent";
|
||||||
|
import {OpenCoWebSiteEvent} from "./Api/Events/OpenCoWebSiteEvent";
|
||||||
|
|
||||||
|
interface WorkAdventureApi {
|
||||||
|
sendChatMessage(message: string, author: string): void;
|
||||||
|
onChatMessage(callback: (message: string) => void): void;
|
||||||
|
onEnterZone(name: string, callback: () => void): void;
|
||||||
|
onLeaveZone(name: string, callback: () => void): void;
|
||||||
|
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup;
|
||||||
|
openTab(url : string): void;
|
||||||
|
goToPage(url : string): void;
|
||||||
|
openCoWebSite(url : string): void;
|
||||||
|
closeCoWebSite(): void;
|
||||||
|
disablePlayerControl() : void;
|
||||||
|
restorePlayerControl() : void;
|
||||||
|
displayBubble() : void;
|
||||||
|
removeBubble() : void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var WA: WorkAdventureApi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatMessageCallback = (message: string) => void;
|
||||||
|
type ButtonClickedCallback = (popup: Popup) => void;
|
||||||
|
|
||||||
|
const userInputChatStream: Subject<UserInputChatEvent> = new Subject();
|
||||||
|
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
|
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
|
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||||
|
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>();
|
||||||
|
|
||||||
|
let popupId = 0;
|
||||||
|
interface ButtonDescriptor {
|
||||||
|
/**
|
||||||
|
* The label of the button
|
||||||
|
*/
|
||||||
|
label: string,
|
||||||
|
/**
|
||||||
|
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
|
||||||
|
*/
|
||||||
|
className?: "normal"|"primary"|"success"|"warning"|"error"|"disabled",
|
||||||
|
/**
|
||||||
|
* Callback called if the button is pressed
|
||||||
|
*/
|
||||||
|
callback: ButtonClickedCallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Popup {
|
||||||
|
constructor(private id: number) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the popup
|
||||||
|
*/
|
||||||
|
public close(): void {
|
||||||
|
window.parent.postMessage({
|
||||||
|
'type': 'closePopup',
|
||||||
|
'data': {
|
||||||
|
'popupId': this.id,
|
||||||
|
} as ClosePopupEvent
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.WA = {
|
||||||
|
/**
|
||||||
|
* Send a message in the chat.
|
||||||
|
* Only the local user will receive this message.
|
||||||
|
*/
|
||||||
|
sendChatMessage(message: string, author: string) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
'type': 'chat',
|
||||||
|
'data': {
|
||||||
|
'message': message,
|
||||||
|
'author': author
|
||||||
|
} as ChatEvent
|
||||||
|
}, '*');
|
||||||
|
},
|
||||||
|
disablePlayerControl() : void {
|
||||||
|
window.parent.postMessage({'type' : 'disablePlayerControl'},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
restorePlayerControl() : void {
|
||||||
|
window.parent.postMessage({'type' : 'restorePlayerControl'},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
displayBubble() : void {
|
||||||
|
window.parent.postMessage({'type' : 'displayBubble'},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
removeBubble() : void {
|
||||||
|
window.parent.postMessage({'type' : 'removeBubble'},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
openTab(url : string) : void{
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type" : 'openTab',
|
||||||
|
"data" : {
|
||||||
|
url
|
||||||
|
} as OpenTabEvent
|
||||||
|
},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
goToPage(url : string) : void{
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type" : 'goToPage',
|
||||||
|
"data" : {
|
||||||
|
url
|
||||||
|
} as GoToPageEvent
|
||||||
|
},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
openCoWebSite(url : string) : void{
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type" : 'openCoWebSite',
|
||||||
|
"data" : {
|
||||||
|
url
|
||||||
|
} as OpenCoWebSiteEvent
|
||||||
|
},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
closeCoWebSite() : void{
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type" : 'closeCoWebSite'
|
||||||
|
},'*');
|
||||||
|
},
|
||||||
|
|
||||||
|
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||||
|
popupId++;
|
||||||
|
const popup = new Popup(popupId);
|
||||||
|
const btnMap = new Map<number, () => void>();
|
||||||
|
popupCallbacks.set(popupId, btnMap);
|
||||||
|
let id = 0;
|
||||||
|
for (const button of buttons) {
|
||||||
|
const callback = button.callback;
|
||||||
|
if (callback) {
|
||||||
|
btnMap.set(id, () => {
|
||||||
|
callback(popup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.parent.postMessage({
|
||||||
|
'type': 'openPopup',
|
||||||
|
'data': {
|
||||||
|
popupId,
|
||||||
|
targetObject,
|
||||||
|
message,
|
||||||
|
buttons: buttons.map((button) => {
|
||||||
|
return {
|
||||||
|
label: button.label,
|
||||||
|
className: button.className
|
||||||
|
};
|
||||||
|
})
|
||||||
|
} as OpenPopupEvent
|
||||||
|
}, '*');
|
||||||
|
|
||||||
|
popups.set(popupId, popup)
|
||||||
|
return popup;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Listen to messages sent by the local user, in the chat.
|
||||||
|
*/
|
||||||
|
onChatMessage(callback: ChatMessageCallback): void {
|
||||||
|
userInputChatStream.subscribe((userInputChatEvent) => {
|
||||||
|
callback(userInputChatEvent.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEnterZone(name: string, callback: () => void): void {
|
||||||
|
let subject = enterStreams.get(name);
|
||||||
|
if (subject === undefined) {
|
||||||
|
subject = new Subject<EnterLeaveEvent>();
|
||||||
|
enterStreams.set(name, subject);
|
||||||
|
}
|
||||||
|
subject.subscribe(callback);
|
||||||
|
},
|
||||||
|
onLeaveZone(name: string, callback: () => void): void {
|
||||||
|
let subject = leaveStreams.get(name);
|
||||||
|
if (subject === undefined) {
|
||||||
|
subject = new Subject<EnterLeaveEvent>();
|
||||||
|
leaveStreams.set(name, subject);
|
||||||
|
}
|
||||||
|
subject.subscribe(callback);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', message => {
|
||||||
|
if (message.source !== window.parent) {
|
||||||
|
return; // Skip message in this event listener
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = message.data;
|
||||||
|
|
||||||
|
console.log(payload);
|
||||||
|
|
||||||
|
if (isIframeEventWrapper(payload)) {
|
||||||
|
const payloadData = payload.data;
|
||||||
|
if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) {
|
||||||
|
userInputChatStream.next(payloadData);
|
||||||
|
} else if (payload.type === 'enterEvent' && isEnterLeaveEvent(payloadData)) {
|
||||||
|
enterStreams.get(payloadData.name)?.next();
|
||||||
|
} else if (payload.type === 'leaveEvent' && isEnterLeaveEvent(payloadData)) {
|
||||||
|
leaveStreams.get(payloadData.name)?.next();
|
||||||
|
} else if (payload.type === 'buttonClickedEvent' && isButtonClickedEvent(payloadData)) {
|
||||||
|
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
||||||
|
const popup = popups.get(payloadData.popupId);
|
||||||
|
if (popup === undefined) {
|
||||||
|
throw new Error('Could not find popup with ID "'+payloadData.popupId+'"');
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback(popup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
});
|
@ -15,6 +15,8 @@ import {MenuScene} from "./Phaser/Menu/MenuScene";
|
|||||||
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
|
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
|
||||||
import {localUserStore} from "./Connexion/LocalUserStore";
|
import {localUserStore} from "./Connexion/LocalUserStore";
|
||||||
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
|
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
|
||||||
|
import {iframeListener} from "./Api/IframeListener";
|
||||||
|
import {discussionManager} from "./WebRtc/DiscussionManager";
|
||||||
|
|
||||||
const {width, height} = coWebsiteManager.getGameSize();
|
const {width, height} = coWebsiteManager.getGameSize();
|
||||||
|
|
||||||
@ -119,3 +121,5 @@ coWebsiteManager.onResize.subscribe(() => {
|
|||||||
const {width, height} = coWebsiteManager.getGameSize();
|
const {width, height} = coWebsiteManager.getGameSize();
|
||||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
iframeListener.init();
|
||||||
|
@ -4,11 +4,15 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './src/index.ts',
|
entry: {
|
||||||
|
'main': './src/index.ts',
|
||||||
|
'iframe_api': './src/iframe_api.ts'
|
||||||
|
},
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: './dist',
|
contentBase: './dist',
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
sockPort: 80,
|
||||||
disableHostCheck: true,
|
disableHostCheck: true,
|
||||||
historyApiFallback: {
|
historyApiFallback: {
|
||||||
rewrites: [
|
rewrites: [
|
||||||
@ -34,7 +38,11 @@ module.exports = {
|
|||||||
extensions: [ '.tsx', '.ts', '.js' ],
|
extensions: [ '.tsx', '.ts', '.js' ],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].[contenthash].js',
|
filename: (pathData) => {
|
||||||
|
// Add a content hash only for the main bundle.
|
||||||
|
// We want the iframe_api.js file to keep its name as it will be referenced from outside iframes.
|
||||||
|
return pathData.chunk.name === 'main' ? 'js/[name].[contenthash].js': '[name].js';
|
||||||
|
},
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
publicPath: '/'
|
publicPath: '/'
|
||||||
},
|
},
|
||||||
@ -54,25 +62,27 @@ module.exports = {
|
|||||||
removeScriptTypeAttributes: true,
|
removeScriptTypeAttributes: true,
|
||||||
removeStyleLinkTypeAttributes: true,
|
removeStyleLinkTypeAttributes: true,
|
||||||
useShortDoctype: true
|
useShortDoctype: true
|
||||||
}
|
},
|
||||||
|
chunks: ['main']
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
Phaser: 'phaser'
|
Phaser: 'phaser'
|
||||||
}),
|
}),
|
||||||
new webpack.EnvironmentPlugin([
|
new webpack.EnvironmentPlugin({
|
||||||
'API_URL',
|
'API_URL': null,
|
||||||
'UPLOADER_URL',
|
'PUSHER_URL': undefined,
|
||||||
'ADMIN_URL',
|
'UPLOADER_URL': null,
|
||||||
'DEBUG_MODE',
|
'ADMIN_URL': null,
|
||||||
'STUN_SERVER',
|
'DEBUG_MODE': null,
|
||||||
'TURN_SERVER',
|
'STUN_SERVER': null,
|
||||||
'TURN_USER',
|
'TURN_SERVER': null,
|
||||||
'TURN_PASSWORD',
|
'TURN_USER': null,
|
||||||
'JITSI_URL',
|
'TURN_PASSWORD': null,
|
||||||
'JITSI_PRIVATE_MODE',
|
'JITSI_URL': null,
|
||||||
'START_ROOM_URL'
|
'JITSI_PRIVATE_MODE': null,
|
||||||
])
|
'START_ROOM_URL': null
|
||||||
|
})
|
||||||
],
|
],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -5362,9 +5362,9 @@ xtend@^4.0.0, xtend@~4.0.1:
|
|||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
|
||||||
|
|
||||||
yallist@^3.0.2:
|
yallist@^3.0.2:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# we are rebuilding on each deploy to cope with the API_URL environment URL
|
|
||||||
FROM thecodingmachine/nodejs:12-apache
|
FROM thecodingmachine/nodejs:12-apache
|
||||||
|
|
||||||
COPY --chown=docker:docker . .
|
COPY --chown=docker:docker . .
|
||||||
|
25
maps/Tuto/Attribution-tilesets.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
CC-BY-SA 3.0:
|
||||||
|
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
- See the file: cc-by-sa-3.0.txt
|
||||||
|
GNU GPL 3.0:
|
||||||
|
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
- See the file: gpl-3.0.txt
|
||||||
|
|
||||||
|
Assets from: workadventure@thecodingmachine.com
|
||||||
|
|
||||||
|
BASE assets:
|
||||||
|
------------
|
||||||
|
|
||||||
|
- le-coq.png
|
||||||
|
- logotcm.png
|
||||||
|
- pin.png
|
||||||
|
- tileset1-repositioning.png
|
||||||
|
- tileset1.png
|
||||||
|
- tileset2.2.png
|
||||||
|
- tileset2.png
|
||||||
|
- tileset3.2.png
|
||||||
|
- tileset3.png
|
||||||
|
- walls2.png
|
BIN
maps/Tuto/Male 13-4.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
maps/Tuto/fantasy.png
Normal file
After Width: | Height: | Size: 395 KiB |
74
maps/Tuto/scriptTuto.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
var isFirstTimeTuto = false;
|
||||||
|
var textFirstPopup = 'Hey ! This is how to start a discussion with someone ! You can be 4 max in a bubble.';
|
||||||
|
var textSecondPopup = 'You can also use the chat to communicate ! ';
|
||||||
|
var targetObjectTutoBubble ='Tutobubble';
|
||||||
|
var targetObjectTutoChat ='tutoChat';
|
||||||
|
var targetObjectTutoExplanation ='tutoExplanation';
|
||||||
|
var popUpExplanation = undefined;
|
||||||
|
function launchTuto (){
|
||||||
|
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
|
||||||
|
{
|
||||||
|
label: "Next",
|
||||||
|
className: "popUpElement",
|
||||||
|
callback: (popup) => {
|
||||||
|
popup.close();
|
||||||
|
|
||||||
|
WA.openPopup(targetObjectTutoChat, textSecondPopup, [
|
||||||
|
{
|
||||||
|
label: "Open Chat",
|
||||||
|
className: "popUpElement",
|
||||||
|
callback: (popup1) => {
|
||||||
|
WA.sendChatMessage("Hey you can talk here too!", 'WA Guide');
|
||||||
|
popup1.close();
|
||||||
|
WA.openPopup("TutoFinal","You are good to go! You can meet the dev team and discover the features in the next room!",[
|
||||||
|
{
|
||||||
|
label: "Got it!",
|
||||||
|
className : "success",callback:(popup2 => {
|
||||||
|
popup2.close();
|
||||||
|
WA.restorePlayerControl();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
WA.disablePlayerControl();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WA.onEnterZone('popupZone', () => {
|
||||||
|
WA.displayBubble();
|
||||||
|
if (!isFirstTimeTuto) {
|
||||||
|
isFirstTimeTuto = true;
|
||||||
|
launchTuto();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
popUpExplanation = WA.openPopup(targetObjectTutoExplanation, 'Do you want to review the explanation?', [
|
||||||
|
{
|
||||||
|
label: "No",
|
||||||
|
className: "error",
|
||||||
|
callback: (popup) => {
|
||||||
|
popup.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Yes",
|
||||||
|
className: "success",
|
||||||
|
callback: (popup) => {
|
||||||
|
popup.close();
|
||||||
|
launchTuto();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
WA.onLeaveZone('popupZone', () => {
|
||||||
|
if (popUpExplanation !== undefined) popUpExplanation.close();
|
||||||
|
WA.removeBubble();
|
||||||
|
})
|
BIN
maps/Tuto/shift.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
maps/Tuto/textTuto3.png
Normal file
After Width: | Height: | Size: 33 KiB |
4
maps/Tuto/tilesets/Atlas-refs-opengameart.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
LPC Atlas :
|
||||||
|
https://opengameart.org/content/lpc-tile-atlas
|
||||||
|
LPC Atlas 2 :
|
||||||
|
https://opengameart.org/content/lpc-tile-atlas2
|
139
maps/Tuto/tilesets/LPC-Atlas-Attribution.txt
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
CC-BY-SA 3.0:
|
||||||
|
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
- See the file: cc-by-sa-3.0.txt
|
||||||
|
GNU GPL 3.0:
|
||||||
|
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
- See the file: gpl-3.0.txt
|
||||||
|
|
||||||
|
Note the file is based on the LCP contest readme so don't expect the exact little pieces used like the base one.
|
||||||
|
*Additional license information.
|
||||||
|
|
||||||
|
Assets from:
|
||||||
|
|
||||||
|
LPC participants:
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Casper Nilsson
|
||||||
|
*GNU GPL 3.0 or later
|
||||||
|
email: casper.nilsson@gmail.com
|
||||||
|
Freenode: CasperN
|
||||||
|
OpenGameArt.org: C.Nilsson
|
||||||
|
|
||||||
|
- LPC C.Nilsson (2D art)
|
||||||
|
|
||||||
|
Daniel Eddeland
|
||||||
|
*GNU GPL 3.0 or later
|
||||||
|
- Tilesets of plants, props, food and environments, suitable for farming / fishing sims and other games.
|
||||||
|
- Includes wheat, grass, sand tilesets, fence tilesets and plants such as corn and tomato.
|
||||||
|
|
||||||
|
|
||||||
|
Johann CHARLOT
|
||||||
|
*GNU LGPL Version 3.
|
||||||
|
*Later versions are permitted.
|
||||||
|
Homepage http://poufpoufproduction.fr
|
||||||
|
Email johannc@poufpoufproduction.fr
|
||||||
|
|
||||||
|
- Shoot'em up graphic kit
|
||||||
|
|
||||||
|
Skyler Robert Colladay
|
||||||
|
|
||||||
|
- FeralFantom's Entry (2D art)
|
||||||
|
|
||||||
|
BASE assets:
|
||||||
|
------------
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- barrel.png
|
||||||
|
- brackish.png
|
||||||
|
- buckets.png
|
||||||
|
- bridges.png
|
||||||
|
- cabinets.png
|
||||||
|
- cement.png
|
||||||
|
- cementstair.png
|
||||||
|
- chests.png
|
||||||
|
- country.png
|
||||||
|
- cup.png
|
||||||
|
- dirt2.png
|
||||||
|
- dirt.png
|
||||||
|
- dungeon.png
|
||||||
|
- grassalt.png
|
||||||
|
- grass.png
|
||||||
|
- holek.png
|
||||||
|
- holemid.png
|
||||||
|
- hole.png
|
||||||
|
- house.png
|
||||||
|
- inside.png
|
||||||
|
- kitchen.png
|
||||||
|
- lava.png
|
||||||
|
- lavarock.png
|
||||||
|
- mountains.png
|
||||||
|
- rock.png
|
||||||
|
- shadow.png
|
||||||
|
- signs.png
|
||||||
|
- stairs.png
|
||||||
|
- treetop.png
|
||||||
|
- trunk.png
|
||||||
|
- waterfall.png
|
||||||
|
- watergrass.png
|
||||||
|
- water.png
|
||||||
|
- princess.png and princess.xcf
|
||||||
|
|
||||||
|
|
||||||
|
Stephen Challener (AKA Redshrike)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- female_walkcycle.png
|
||||||
|
- female_hurt.png
|
||||||
|
- female_slash.png
|
||||||
|
- female_spellcast.png
|
||||||
|
- male_walkcycle.png
|
||||||
|
- male_hurt.png
|
||||||
|
- male_slash.png
|
||||||
|
- male_spellcast.png
|
||||||
|
- male_pants.png
|
||||||
|
- male_hurt_pants.png
|
||||||
|
- male_fall_down_pants.png
|
||||||
|
- male_slash_pants.png
|
||||||
|
|
||||||
|
|
||||||
|
Charles Sanchez (AKA CharlesGabriel)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- bat.png
|
||||||
|
- bee.png
|
||||||
|
- big_worm.png
|
||||||
|
- eyeball.png
|
||||||
|
- ghost.png
|
||||||
|
- man_eater_flower.png
|
||||||
|
- pumpking.png
|
||||||
|
- slime.png
|
||||||
|
- small_worm.png
|
||||||
|
- snake.png
|
||||||
|
|
||||||
|
|
||||||
|
Manuel Riecke (AKA MrBeast)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- hairfemale.png and hairfemale.xcf
|
||||||
|
- hairmale.png and hairmale.xcf
|
||||||
|
- soldier.png
|
||||||
|
- soldier_altcolor.png
|
||||||
|
|
||||||
|
|
||||||
|
Daniel Armstrong (AKA HughSpectrum)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Castle work:
|
||||||
|
|
||||||
|
- castlewalls.png
|
||||||
|
- castlefloors.png
|
||||||
|
- castle_outside.png
|
||||||
|
- castlefloors_outside.png
|
||||||
|
- castle_lightsources.png
|
||||||
|
|
||||||
|
|
166
maps/Tuto/tilesets/LPC-Atlas2-Attribution2.txt
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
CC-BY-SA 3.0:
|
||||||
|
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
- See the file: cc-by-sa-3.0.txt
|
||||||
|
GNU GPL 3.0:
|
||||||
|
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
- See the file: gpl-3.0.txt
|
||||||
|
|
||||||
|
Note only some files from the entries are selected.
|
||||||
|
*Additional license information.
|
||||||
|
|
||||||
|
Assets from:
|
||||||
|
|
||||||
|
LPC participants:
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Barbara Rivera
|
||||||
|
|
||||||
|
- tree,tombstone
|
||||||
|
|
||||||
|
Casper Nilsson
|
||||||
|
*GNU GPL 3.0 or later
|
||||||
|
email: casper.nilsson@gmail.com
|
||||||
|
Freenode: CasperN
|
||||||
|
OpenGameArt.org: C.Nilsson
|
||||||
|
|
||||||
|
- LPC C.Nilsson (2D art)
|
||||||
|
|
||||||
|
Chris Phillips
|
||||||
|
|
||||||
|
- tree
|
||||||
|
|
||||||
|
Daniel Eddeland
|
||||||
|
*GNU GPL 3.0 or later
|
||||||
|
|
||||||
|
- Tilesets of plants, props, food and environments, suitable for farming / fishing sims and other games.
|
||||||
|
- Includes wheat, grass, sand tilesets, fence tilesets and plants such as corn and tomato.
|
||||||
|
- Also includes village/marketplace objects like sacks, food, some smithing equipment, tables and stalls.
|
||||||
|
|
||||||
|
|
||||||
|
Anamaris and Krusmira (aka? Emilio J Sanchez)
|
||||||
|
|
||||||
|
- Sierra__Steampun-a-fy (with concept art)
|
||||||
|
|
||||||
|
Jonas Klinger
|
||||||
|
|
||||||
|
- Skorpio's SciFi Sprite Pack
|
||||||
|
|
||||||
|
Joshua Taylor
|
||||||
|
|
||||||
|
- Fruit and Veggie Inventory
|
||||||
|
|
||||||
|
Leo Villeveygoux
|
||||||
|
|
||||||
|
- Limestone Wall
|
||||||
|
|
||||||
|
Mark Weyer
|
||||||
|
|
||||||
|
- signpost+shadow
|
||||||
|
|
||||||
|
Matthew Nash
|
||||||
|
|
||||||
|
- Public Toilet Tileset
|
||||||
|
|
||||||
|
Skyler Robert Colladay
|
||||||
|
|
||||||
|
- FeralFantom's Entry
|
||||||
|
|
||||||
|
|
||||||
|
BASE assets:
|
||||||
|
------------
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- barrel.png
|
||||||
|
- brackish.png
|
||||||
|
- buckets.png
|
||||||
|
- bridges.png
|
||||||
|
- cabinets.png
|
||||||
|
- cement.png
|
||||||
|
- cementstair.png
|
||||||
|
- chests.png
|
||||||
|
- country.png
|
||||||
|
- cup.png
|
||||||
|
- dirt2.png
|
||||||
|
- dirt.png
|
||||||
|
- dungeon.png
|
||||||
|
- grassalt.png
|
||||||
|
- grass.png
|
||||||
|
- holek.png
|
||||||
|
- holemid.png
|
||||||
|
- hole.png
|
||||||
|
- house.png
|
||||||
|
- inside.png
|
||||||
|
- kitchen.png
|
||||||
|
- lava.png
|
||||||
|
- lavarock.png
|
||||||
|
- mountains.png
|
||||||
|
- rock.png
|
||||||
|
- shadow.png
|
||||||
|
- signs.png
|
||||||
|
- stairs.png
|
||||||
|
- treetop.png
|
||||||
|
- trunk.png
|
||||||
|
- waterfall.png
|
||||||
|
- watergrass.png
|
||||||
|
- water.png
|
||||||
|
- princess.png and princess.xcf
|
||||||
|
|
||||||
|
|
||||||
|
Stephen Challener (AKA Redshrike)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- female_walkcycle.png
|
||||||
|
- female_hurt.png
|
||||||
|
- female_slash.png
|
||||||
|
- female_spellcast.png
|
||||||
|
- male_walkcycle.png
|
||||||
|
- male_hurt.png
|
||||||
|
- male_slash.png
|
||||||
|
- male_spellcast.png
|
||||||
|
- male_pants.png
|
||||||
|
- male_hurt_pants.png
|
||||||
|
- male_fall_down_pants.png
|
||||||
|
- male_slash_pants.png
|
||||||
|
|
||||||
|
|
||||||
|
Charles Sanchez (AKA CharlesGabriel)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- bat.png
|
||||||
|
- bee.png
|
||||||
|
- big_worm.png
|
||||||
|
- eyeball.png
|
||||||
|
- ghost.png
|
||||||
|
- man_eater_flower.png
|
||||||
|
- pumpking.png
|
||||||
|
- slime.png
|
||||||
|
- small_worm.png
|
||||||
|
- snake.png
|
||||||
|
|
||||||
|
|
||||||
|
Manuel Riecke (AKA MrBeast)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- hairfemale.png and hairfemale.xcf
|
||||||
|
- hairmale.png and hairmale.xcf
|
||||||
|
- soldier.png
|
||||||
|
- soldier_altcolor.png
|
||||||
|
|
||||||
|
|
||||||
|
Daniel Armstrong (AKA HughSpectrum)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Castle work:
|
||||||
|
|
||||||
|
- castlewalls.png
|
||||||
|
- castlefloors.png
|
||||||
|
- castle_outside.png
|
||||||
|
- castlefloors_outside.png
|
||||||
|
- castle_lightsources.png
|
||||||
|
|
||||||
|
|
2
maps/Tuto/tilesets/LPC-indoors.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
https://opengameart.org/content/lpc-interior-castle-tiles
|
||||||
|
credit Lanea Zimmerman
|
55
maps/Tuto/tilesets/LPC-interiors-house-credits.txt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
= Wooden floor(CC-BY-SA) =
|
||||||
|
* Horizontal wooden floor by Lanea Zimmerman (AKA Sharm)
|
||||||
|
* Horizontal wooden floor with hole by Lanea Zimmerman (AKA Sharm) and Tuomo Untinen
|
||||||
|
* Vertical wooden floor by Tuomo Untinen
|
||||||
|
* Vertical wooden floor with hole by Tuomo Untinen
|
||||||
|
= Wooden wall topping (CC-BY-SA) =
|
||||||
|
* Tuomo Untinen
|
||||||
|
= Pile of barrels =
|
||||||
|
* Based LPC base tiles by Lanea Zimmerman (AKA Sharm)
|
||||||
|
= Decorational stuff (CC-BY-SA) =
|
||||||
|
* Green bottle
|
||||||
|
* Wine glass and bottle
|
||||||
|
* Hanging sacks
|
||||||
|
* Wall mounted rope and ropes
|
||||||
|
* Wall mounted swords
|
||||||
|
* Wall mounted kite shield
|
||||||
|
* Wall hole
|
||||||
|
By Tuomo Untinen
|
||||||
|
* Small sack from LPC farming tileset by Daniel Eddeland (http://opengameart.org/content/lpc- farming-tilesets-magic-animations-and-ui-elements)
|
||||||
|
* Purple bottles and gray lantern from Hyptosis Mage city
|
||||||
|
* Green and blue bottle by Tuomo Untinen
|
||||||
|
= Wall clock (CC-BY-SA) =
|
||||||
|
* Lanea Zimmerman AKA Sharm
|
||||||
|
* Tuomo Untinen (Scaled down and animation)
|
||||||
|
= Stone floor (CC-BY-SA)=
|
||||||
|
* Tuomo Untinen
|
||||||
|
= Cobble stone floor (CC-BY-SA)=
|
||||||
|
* Based on LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||||
|
= Cabinets and kitchen stuff including metal stove(CC-BY-SA) =
|
||||||
|
* Based on LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||||
|
* Cutboard is made by Hyptosis
|
||||||
|
* Sacks based on LPC farming tileset by Daniel Eddeland (http://opengameart.org/content/lpc- farming-tilesets-magic-animations-and-ui-elements)
|
||||||
|
* Spears by Tuomo Untinen
|
||||||
|
* Vertical chest by Tuomo Untinen based on LPC base tiles Lanea Zimmerman (AKA Sharm)
|
||||||
|
Manuel Riecke (AKA MrBeast)
|
||||||
|
= Skull (CC-BY-SA) =
|
||||||
|
* http://opengameart.org/content/lpc-dungeon-elements
|
||||||
|
* Graphical artist Lanea Zimmerman AKA Sharm
|
||||||
|
* Contributor William Thompson
|
||||||
|
= pile sacks =
|
||||||
|
* LPC farming tileset by Daniel Eddeland (http://opengameart.org/content/lpc- farming-tilesets-magic-animations-and-ui-elements)
|
||||||
|
= Pile of papers(CC-BY-SA) =
|
||||||
|
* Based on caeles papers
|
||||||
|
= Armor shelves(CC-BY-SA) =
|
||||||
|
* Based on LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||||
|
* Armors by: Adapted by Matthew Krohn from art created by Johannes Sjölund
|
||||||
|
= Table lamp =
|
||||||
|
* Tuomo Untinen
|
||||||
|
= Distiller =
|
||||||
|
* Table is from LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||||
|
* Distiller by Tuomo Untinen
|
||||||
|
= Fireplace =
|
||||||
|
* Tuomo Untinen
|
||||||
|
* Inspired by Lanea Zimmerman (AKA Sharm) Fireplace
|
||||||
|
|
28
maps/Tuto/tilesets/LPC-leaf-credits.txt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
CC-BY-SA 3.0:
|
||||||
|
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
GNU GPL 3.0:
|
||||||
|
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
If you need to figure out exactly who made what please see the Liberated Pixel Cup entries.
|
||||||
|
|
||||||
|
Liberated Pixel Cup Assets:
|
||||||
|
http://opengameart.org/lpc-art-entries
|
||||||
|
|
||||||
|
LPC participants:
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Johann CHARLOT
|
||||||
|
Homepage http://poufpoufproduction.fr
|
||||||
|
Email johannc@poufpoufproduction.fr
|
||||||
|
- Shoot'em up graphic kit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Recolored Leaves
|
||||||
|
|
||||||
|
William Thompson
|
||||||
|
Email: william.thompsonj@gmail.com
|
||||||
|
OpenGameArt.org: williamthompsonj
|
1
maps/Tuto/tilesets/LPC-leafs-refs.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://opengameart.org/content/lpc-leaf-recolor
|
1
maps/Tuto/tilesets/LPC-submissions-refs.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://opengameart.org/content/lpc-submissions-merged
|
1
maps/Tuto/tilesets/LPc-mountains-refs.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://opengameart.org/content/lpc-mountains
|
344
maps/Tuto/tilesets/LPc-submissions-Final Attribution.txt
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
CC-BY-SA 3.0:
|
||||||
|
|
||||||
|
http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
|
||||||
|
GNU GPL 3.0:
|
||||||
|
|
||||||
|
http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Terrain and Outside:
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm) - CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- barrels
|
||||||
|
- darkishgreen water
|
||||||
|
- buckets
|
||||||
|
- bridges
|
||||||
|
- cement
|
||||||
|
- cement stairs
|
||||||
|
- chests
|
||||||
|
- cup
|
||||||
|
- light dirt
|
||||||
|
- mid dirt
|
||||||
|
- dungeon
|
||||||
|
- grass1 (leftmost)
|
||||||
|
- grass2 (Middle grass)
|
||||||
|
- hole1 (left hole near lava)
|
||||||
|
- hole2 (middle hole)
|
||||||
|
- hole3 (black whole next to transparent water)
|
||||||
|
- lava
|
||||||
|
- lavarock (black dirt)
|
||||||
|
- mountains ridge (right of the water tiles)
|
||||||
|
- white rocks
|
||||||
|
- waterfall
|
||||||
|
- water/grass
|
||||||
|
- water (transparent water beside black hole)
|
||||||
|
|
||||||
|
Daniel Eddeland CC-BY-SA-3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-farming-tilesets-magic-animations-and-ui-elements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Plowed Ground
|
||||||
|
- Water reeds
|
||||||
|
- Sand
|
||||||
|
- Sand with water
|
||||||
|
- Tall grass
|
||||||
|
- Wheat
|
||||||
|
- Young wheet (green wheat left of tall grass)
|
||||||
|
|
||||||
|
William Thompsonj
|
||||||
|
https://opengameart.org/content/lpc-sandrock-alt-colors
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- sand (near the wheat)
|
||||||
|
- sand with water
|
||||||
|
- grey dirt left of the lava)
|
||||||
|
|
||||||
|
|
||||||
|
Matthew Krohn (Makrohn)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Cave / Mountain steps originals from Lanea Zimmerman
|
||||||
|
|
||||||
|
Matthew Nash
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- stone tile (purplish color beside the dirt path bottom right corner)
|
||||||
|
|
||||||
|
Nushio
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Ice tiles
|
||||||
|
- Snow
|
||||||
|
- snow/ice
|
||||||
|
- Snow water
|
||||||
|
|
||||||
|
|
||||||
|
Casper Nilson
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- grass with flowers
|
||||||
|
- stone pattern below cement
|
||||||
|
- tree stumps
|
||||||
|
- lily pads
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
MISSING:
|
||||||
|
Bricks / Paths above lillypads and left of barrels
|
||||||
|
The recoloing of the rocks on the left
|
||||||
|
The bigger stump
|
||||||
|
Bottom right tiles
|
||||||
|
Outside stone head and columns
|
||||||
|
Green water
|
||||||
|
Ladders
|
||||||
|
Brown path
|
||||||
|
Sewer
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Outside Objects:
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm) CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Tree trunks (for evergreen and oak)
|
||||||
|
- Tree Tops (for evergreen and oak)
|
||||||
|
|
||||||
|
Daniel Eddeland
|
||||||
|
(https://opengameart.org/content/lpc-farming-tilesets-magic-animations-and-ui-elements)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Farming stuff including food / crops / sack of grains
|
||||||
|
- Logs / Anvils
|
||||||
|
- Fish / boats / pier
|
||||||
|
- Bazaar / Merchant displays
|
||||||
|
- Wooden Fences
|
||||||
|
|
||||||
|
Caeless
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Shadowless veggies (originally made by Daniel Eddeland)
|
||||||
|
|
||||||
|
William Thompsonj
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- fallen leaves
|
||||||
|
|
||||||
|
Casper Nilsson
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Metal Fence
|
||||||
|
- Wheelbarrows
|
||||||
|
- tent
|
||||||
|
- Gravestones
|
||||||
|
- harbor (stone platform in water)
|
||||||
|
- long boat
|
||||||
|
|
||||||
|
Barbara Rivera / C Phillips
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Leafless tree
|
||||||
|
|
||||||
|
Skorpio
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Trash / Barrel ( Top right corner)
|
||||||
|
|
||||||
|
MISSING:
|
||||||
|
Bricks bottom left
|
||||||
|
- Mushrooms need attributions
|
||||||
|
Bricks/tiles above pier
|
||||||
|
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Exterior:
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm) CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- house (light red brick wall on far left and purple roof below it)
|
||||||
|
- house white door frame / brown door and white windows.
|
||||||
|
- signs under gold and drak brown brick house wall
|
||||||
|
|
||||||
|
Daniel Armstrong (AKA HughSpectrum)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- castle walls
|
||||||
|
- castle floors
|
||||||
|
- castle outside
|
||||||
|
- castle floors outside
|
||||||
|
|
||||||
|
Xenodora CC-BY-3.0 / GPL 3.0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- water well
|
||||||
|
|
||||||
|
Tuomo Untinen CC-BY-3.0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- stone bridge
|
||||||
|
- silver windows
|
||||||
|
|
||||||
|
Casper Nilsson
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Asain themed shrine including red lantern
|
||||||
|
- foodog statue
|
||||||
|
- Toro
|
||||||
|
- Cherry blossom tree
|
||||||
|
- Torii
|
||||||
|
- Grey Roof
|
||||||
|
- Flag poles
|
||||||
|
- Fancy white and black entrance
|
||||||
|
- white door
|
||||||
|
- green and white walls / roof / windows
|
||||||
|
- shrub plant in white square pot
|
||||||
|
- flower box
|
||||||
|
Steampun-a-fy: Amaris / Krusimira
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- dark purple brick near purple roof
|
||||||
|
- bronze and wood house (gold and dark brick)
|
||||||
|
- dark wooden stairs
|
||||||
|
- gold and dark chimney
|
||||||
|
- grey door
|
||||||
|
- gears
|
||||||
|
- pipes
|
||||||
|
- dark wooden windows
|
||||||
|
|
||||||
|
Leo Villeveygoux
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- white bricks (limestone wall)
|
||||||
|
|
||||||
|
Skyler Robert Collady
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Grey home assets
|
||||||
|
|
||||||
|
MISSING attributions:
|
||||||
|
Graves bottom right
|
||||||
|
Water filled boat
|
||||||
|
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Interior
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm) CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- bookshelves (to the left of light brown chairs)
|
||||||
|
- cabinents above bookshelves
|
||||||
|
- counters (to the right of the kitchen table (between the white bathroom tiles) and bottom left by the rug.
|
||||||
|
- blue wallpaper
|
||||||
|
- kitchen furnace, pots and sink.
|
||||||
|
- country inside (blue bed and light brown chairs)
|
||||||
|
- red royal bed with white pillows.
|
||||||
|
- Mahogany kitchen table (near the dungeon bed with a black blanket)
|
||||||
|
- Yellow curtains
|
||||||
|
- Flower pot with tall pink flower
|
||||||
|
- Empty flower pots
|
||||||
|
- Chairs with gold seats (between the fireplaces)
|
||||||
|
- Fireplaces
|
||||||
|
- White and red mahogany stairs
|
||||||
|
- Double Rounded doors
|
||||||
|
- Flower vases
|
||||||
|
- Purple/Blue Tiles near white brick wall
|
||||||
|
- White brick wall
|
||||||
|
- White Columns
|
||||||
|
- Black candle-holder stand with candles
|
||||||
|
- Royal rug beside cabinents
|
||||||
|
- White stairs with runway / platform
|
||||||
|
- Grandfather clock
|
||||||
|
- Blue wallpaper with woodem trim
|
||||||
|
- Wood tiles
|
||||||
|
- Long painting
|
||||||
|
- Royal chairs ( gold seats)
|
||||||
|
- Rounded white windows
|
||||||
|
- Portrait painting
|
||||||
|
- small end / round tables
|
||||||
|
- Royal bed with red pillows
|
||||||
|
- white china
|
||||||
|
|
||||||
|
By Sharm but Commissioned by William Thompsonj
|
||||||
|
- campfire
|
||||||
|
- skeletons
|
||||||
|
- dungeon beds
|
||||||
|
- wood chairs and tables between the calderon and the fancy door rounded door
|
||||||
|
- calderon
|
||||||
|
- cobwebs
|
||||||
|
- dungeon prison wall and door/gate (beside fancy red and gold rugs)
|
||||||
|
- dirt by the dungeon beds
|
||||||
|
- rat
|
||||||
|
|
||||||
|
|
||||||
|
Daniel Armstrong (Aka HughSpectrum)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- castle light Sources (torches)
|
||||||
|
- red carpets
|
||||||
|
- grey brick top of walls
|
||||||
|
|
||||||
|
Matthew Nash
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Public Toilets
|
||||||
|
- Bathroom tiles (white and black)
|
||||||
|
|
||||||
|
Tuomo Untinen
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Pots with cover (based from Sharm's pots)
|
||||||
|
- Yellow stone floor
|
||||||
|
- Short paintings
|
||||||
|
- royal chair modification
|
||||||
|
- cupboards based on Sharm's (with china)
|
||||||
|
- small footchair
|
||||||
|
- piano
|
||||||
|
|
||||||
|
|
||||||
|
MISSING attributions:
|
||||||
|
Some bottomleft tiles
|
||||||
|
Banners
|
||||||
|
Sideways table
|
||||||
|
Stacked barrels
|
||||||
|
Stacked chess
|
||||||
|
Things to left of the pots
|
||||||
|
Some of the furniture below the beds
|
||||||
|
Some of the single beds
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
*Interior 2
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- brown stairs with blue
|
||||||
|
- Fountain
|
||||||
|
- Pool
|
||||||
|
- FLoor tiles
|
||||||
|
- Everything between top row (between toilet stalls and bookcases) down to the floor tiles
|
||||||
|
|
||||||
|
|
||||||
|
Xenodora
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Steel floor
|
||||||
|
|
||||||
|
|
||||||
|
Tuomo Untinen
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Armor and sheilds
|
||||||
|
- Cheese and bread
|
||||||
|
- Ship
|
||||||
|
|
||||||
|
|
||||||
|
Janna - CC0
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Beds
|
||||||
|
- Dressers / book shelves
|
||||||
|
- Wardrobe
|
||||||
|
|
||||||
|
|
||||||
|
Casper Nilsson
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Red and Blue stairs
|
||||||
|
|
||||||
|
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Extensions Folder:
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Lanea Zimmerman (AKA Sharm)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
https://opengameart.org/content/lpc-adobe-building-set
|
||||||
|
https://opengameart.org/content/lpc-arabic-elements
|
||||||
|
- Adobe / Arabic - Commissioned by William Thompsonj
|
12
maps/Tuto/tilesets/Specials-attribution.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
CC-BY-SA 3.0:
|
||||||
|
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
|
||||||
|
Author :
|
||||||
|
|
||||||
|
Jacques-Olivier Farcy
|
||||||
|
https://interstices.ouvaton.org
|
||||||
|
https://twitter.com/JO_Interstices
|
||||||
|
|
106
maps/Tuto/tilesets/outdoor/CREDITS-plants.txt
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
## Flowers / Plants / Fungi / Wood
|
||||||
|
|
||||||
|
"[LPC] Flowers / Plants / Fungi / Wood," by bluecarrot16, Guido Bos, Ivan Voirol (Silver IV), SpiderDave, William.Thompsonj, Yar, Stephen Challener and the Open Surge team (http://opensnc.sourceforge.net), Gaurav Munjal, Johann Charlot, Casper Nilsson, Jetrel, Zabin, Hyptosis, Surt, Lanea Zimmerman, George Bailey, ansimuz, Buch, and the Open Pixel Project contributors (OpenPixelProject.com).
|
||||||
|
CC-BY-SA 3.0.
|
||||||
|
|
||||||
|
Based on:
|
||||||
|
|
||||||
|
[LPC] Guido Bos entries cut up
|
||||||
|
Guido Bos
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-guido-bos-entries-cut-up
|
||||||
|
|
||||||
|
Basic map 32x32 by Silver IV
|
||||||
|
Ivan Voirol (Silver IV)
|
||||||
|
CC-BY 3.0 / GPL 3.0 / GPL 2.0
|
||||||
|
https://opengameart.org/content/basic-map-32x32-by-silver-iv
|
||||||
|
|
||||||
|
Flowers
|
||||||
|
SpiderDave
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/flowers
|
||||||
|
|
||||||
|
[LPC] Leaf Recolor
|
||||||
|
William.Thompsonj
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-leaf-recolor
|
||||||
|
|
||||||
|
Isometric 64x64 Outside Tileset
|
||||||
|
Yar
|
||||||
|
CC-BY 3.0
|
||||||
|
https://opengameart.org/content/isometric-64x64-outside-tileset
|
||||||
|
|
||||||
|
32x32 (and 16x16) RPG Tiles--Forest and some Interior Tiles
|
||||||
|
Stephen Challener and the Open Surge team (http://opensnc.sourceforge.net)commissioned by Gaurav Munjal
|
||||||
|
CC-BY 3.0
|
||||||
|
https://opengameart.org/content/32x32-and-16x16-rpg-tiles-forest-and-some-interior-tiles
|
||||||
|
|
||||||
|
Lots of Hyptosis' tiles organized!
|
||||||
|
Hyptosis
|
||||||
|
CC-BY 3.0
|
||||||
|
https://opengameart.org/content/lots-of-hyptosis-tiles-organized
|
||||||
|
|
||||||
|
Generic Platformer Tiles
|
||||||
|
surt
|
||||||
|
CC0
|
||||||
|
http://opengameart.org/content/generic-platformer-tiles
|
||||||
|
|
||||||
|
old frogatto tile art
|
||||||
|
Guido Bos
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/old-frogatto-tile-art
|
||||||
|
|
||||||
|
LPC: Interior Castle Tiles
|
||||||
|
Lanea Zimmerman
|
||||||
|
CC-BY-3.0 / GPL 3.0
|
||||||
|
http://opengameart.org/content/lpc-interior-castle-tiles
|
||||||
|
|
||||||
|
RPG item set
|
||||||
|
Jetrel
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/rpg-item-set
|
||||||
|
|
||||||
|
Shoot'em up graphic kit
|
||||||
|
Johann Charlot
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/shootem-up-graphic-kit
|
||||||
|
|
||||||
|
LPC C.Nilsson
|
||||||
|
Casper Nilsson
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-cnilsson
|
||||||
|
|
||||||
|
Lots of trees and plants from OGA (DB32) tilesets pack 1
|
||||||
|
Jetrel, Zabin, Hyptosis, Surt
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/lots-of-trees-and-plants-from-oga-db32-tilesets-pack-1
|
||||||
|
|
||||||
|
Trees & Bushes
|
||||||
|
ansimuz
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/trees-bushes
|
||||||
|
|
||||||
|
Outdoor tiles, again
|
||||||
|
Buch <https://opengameart.org/users/buch>
|
||||||
|
CC-BY 2.0
|
||||||
|
https://opengameart.org/content/outdoor-tiles-again
|
||||||
|
|
||||||
|
16x16 Game Assets
|
||||||
|
George Bailey
|
||||||
|
CC-BY 4.0
|
||||||
|
https://opengameart.org/content/16x16-game-assets
|
||||||
|
|
||||||
|
Tuxemon tileset
|
||||||
|
Buch
|
||||||
|
CC-BY-SA 3.0
|
||||||
|
https://opengameart.org/content/tuxemon-tileset
|
||||||
|
|
||||||
|
Orthographic outdoor tiles
|
||||||
|
Buch
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/orthographic-outdoor-tiles
|
||||||
|
|
||||||
|
OPP2017 - Jungle and temple set
|
||||||
|
OpenPixelProject.com
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/opp2017-jungle-and-temple-set
|
After Width: | Height: | Size: 295 KiB |
BIN
maps/Tuto/tilesets/outdoor/LPC-submissions-outside.png
Normal file
After Width: | Height: | Size: 192 KiB |
BIN
maps/Tuto/tilesets/outdoor/LPC-terrains-subimissions-outside.png
Normal file
After Width: | Height: | Size: 327 KiB |
101
maps/Tuto/tilesets/outdoor/decorations-medieval-credits.txt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
## Medieval
|
||||||
|
|
||||||
|
[LPC] Hanging signs
|
||||||
|
Reemax
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-hanging-signs
|
||||||
|
|
||||||
|
Liberated Pixel Cup (LPC) Base Assets
|
||||||
|
Lanea Zimmerman (Sharm)
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/liberated-pixel-cup-lpc-base-assets-sprites-map-tiles
|
||||||
|
|
||||||
|
[LPC] City outside
|
||||||
|
Reemax (Tuomo Untinen), Xenodora, Sharm, Johann C, Johannes Sjölund
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0 / GPL 2.0
|
||||||
|
https://opengameart.org/content/lpc-city-outside
|
||||||
|
|
||||||
|
[LPC] Cavern and ruin tiles
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0 / GPL 2.0
|
||||||
|
Reemax, Sharm, Hyptosis, Johann C, HughSpectrum, Redshrike, William.Thompsonj, wulax,
|
||||||
|
https://opengameart.org/node/33913
|
||||||
|
|
||||||
|
Statues & Fountains Collection
|
||||||
|
Casper Nilsson, Daniel Cook, Rayane Félix (RayaneFLX), Wolthera van Hövell tot Westerflier (TheraHedwig), Hyptosis, mold, Zachariah Husiar (Zabin), & Clint Bellanger
|
||||||
|
CC-BY-SA 3.0
|
||||||
|
https://opengameart.org/content/statues-fountains-collection
|
||||||
|
|
||||||
|
LPC C.Nilsson
|
||||||
|
Casper Nilsson
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-cnilsson
|
||||||
|
|
||||||
|
LPC Style Well
|
||||||
|
CC-BY 3.0 / GPL 3.0
|
||||||
|
Xenodora, Sharm
|
||||||
|
https://opengameart.org/content/lpc-style-well
|
||||||
|
|
||||||
|
RPG item set
|
||||||
|
Jetrel
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/rpg-item-set
|
||||||
|
|
||||||
|
[LPC] Guido Bos entries cut up
|
||||||
|
Guido Bos
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-guido-bos-entries-cut-up
|
||||||
|
|
||||||
|
LPC Sign Post
|
||||||
|
Nemisys
|
||||||
|
CC-BY 3.0 / CC-BY-SA 3.0 / GPL 3.0 / OGA-BY 3.0
|
||||||
|
https://opengameart.org/content/lpc-sign-post
|
||||||
|
|
||||||
|
[LPC] Signposts, graves, line cloths and scare crow
|
||||||
|
Reemax
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-signposts-graves-line-cloths-and-scare-crow
|
||||||
|
|
||||||
|
[LPC] Hanging signs
|
||||||
|
Reemax
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
https://opengameart.org/content/lpc-hanging-signs
|
||||||
|
|
||||||
|
Hyptosis
|
||||||
|
Mage City Arcanos
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/mage-city-arcanos
|
||||||
|
|
||||||
|
[LPC] Street Lamp
|
||||||
|
Curt
|
||||||
|
CC-BY 3.0
|
||||||
|
https://opengameart.org/content/lpc-street-lamp
|
||||||
|
|
||||||
|
[LPC] Misc
|
||||||
|
Lanea Zimmerman (Sharm), William.Thompsonj
|
||||||
|
CC-BY 3.0 / GPL 3.0 / GPL 2.0 / OGA-BY 3.0
|
||||||
|
https://opengameart.org/content/lpc-misc
|
||||||
|
|
||||||
|
RPG Tiles: Cobble stone paths & town objects
|
||||||
|
https://opengameart.org/content/rpg-tiles-cobble-stone-paths-town-objects
|
||||||
|
Zabin, Daneeklu, Jetrel, Hyptosis, Redshrike, Bertram.
|
||||||
|
CC-BY-SA 3.0
|
||||||
|
|
||||||
|
[LPC] Farming tilesets, magic animations and UI elements
|
||||||
|
https://opengameart.org/content/lpc-farming-tilesets-magic-animations-and-ui-elements
|
||||||
|
Daniel Eddeland (daneeklu)
|
||||||
|
CC-BY-SA 3.0 / GPL 3.0
|
||||||
|
|
||||||
|
RPG item set
|
||||||
|
Jetrel
|
||||||
|
CC0
|
||||||
|
https://opengameart.org/content/rpg-item-set
|
||||||
|
|
||||||
|
RPG Indoor Tileset: Expansion 1
|
||||||
|
Redshrike
|
||||||
|
CC-BY 3.0 / GPL 3.0 / GPL 2.0 / OGA-BY 3.0
|
||||||
|
https://opengameart.org/content/rpg-indoor-tileset-expansion-1
|
||||||
|
|
||||||
|
[LPC] Dungeon Elements
|
||||||
|
Lanea Zimmerman (Sharm), William.Thompsonj
|
||||||
|
CC-BY 3.0 / GPL 3.0 / GPL 2.0 / OGA-BY 3.0
|
||||||
|
https://opengameart.org/content/lpc-dungeon-elements
|
BIN
maps/Tuto/tilesets/outdoor/decorations-medieval.png
Normal file
After Width: | Height: | Size: 165 KiB |
BIN
maps/Tuto/tilesets/outdoor/plants.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
maps/Tuto/tilesets/specials/ALL.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
706
maps/Tuto/tutoV3.json
Normal file
99
maps/tests/goToPage.json
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":20,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":20,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":20,
|
||||||
|
"id":4,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":20,
|
||||||
|
"id":3,
|
||||||
|
"name":"popupZone",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"zone",
|
||||||
|
"type":"string",
|
||||||
|
"value":"popUpGoToPageZone"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":5,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":59,
|
||||||
|
"id":1,
|
||||||
|
"name":"popUp",
|
||||||
|
"rotation":0,
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":152,
|
||||||
|
"x":247,
|
||||||
|
"y":11
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":6,
|
||||||
|
"nextobjectid":2,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"goToPageScript.js"
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.5.0",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.5,
|
||||||
|
"width":20
|
||||||
|
}
|
49
maps/tests/goToPageScript.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
var zoneName = "popUpGoToPageZone";
|
||||||
|
var urlPricing = "https://workadventu.re/pricing";
|
||||||
|
var urlGettingStarted = "https://workadventu.re/getting-started";
|
||||||
|
var isCoWebSiteOpened = false;
|
||||||
|
|
||||||
|
WA.onChatMessage((message => {
|
||||||
|
WA.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot');
|
||||||
|
}));
|
||||||
|
|
||||||
|
WA.onEnterZone(zoneName, () => {
|
||||||
|
WA.openPopup("popUp","Open Links",[
|
||||||
|
{
|
||||||
|
label: "Open Tab",
|
||||||
|
className: "popUpElement",
|
||||||
|
callback: (popup => {
|
||||||
|
WA.openTab(urlPricing);
|
||||||
|
popup.close();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Go To Page", className : "popUpElement",
|
||||||
|
callback:(popup => {
|
||||||
|
WA.goToPage(urlPricing);
|
||||||
|
popup.close();
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
label: "openCoWebSite", className : "popUpElement",
|
||||||
|
callback:(popup => {
|
||||||
|
WA.openCoWebSite(urlPricing);
|
||||||
|
isCoWebSiteOpened = true;
|
||||||
|
popup.close();
|
||||||
|
})
|
||||||
|
|
||||||
|
}]);
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onLeaveZone(zoneName, () => {
|
||||||
|
if (isCoWebSiteOpened) {
|
||||||
|
WA.closeCoWebSite();
|
||||||
|
isCoWebSiteOpened = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onLeaveZone('popupZone', () => {
|
||||||
|
|
||||||
|
})
|
25
maps/tests/iframe.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script src="http://play.workadventure.localhost/iframe_api.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button id="sendchat">Send chat message</button>
|
||||||
|
<script>
|
||||||
|
document.getElementById('sendchat').onclick = () => {
|
||||||
|
WA.sendChatMessage('Hello world!', 'Mr Robot');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div id="chatSent"></div>
|
||||||
|
<script>
|
||||||
|
WA.onChatMessage((message => {
|
||||||
|
const chatDiv = document.createElement('p');
|
||||||
|
chatDiv.innerText = message;
|
||||||
|
document.getElementById('chatSent').append(chatDiv);
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
94
maps/tests/iframe_api.json
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"editorsettings":
|
||||||
|
{
|
||||||
|
"export":
|
||||||
|
{
|
||||||
|
"target":"."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height":10,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":10,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":5,
|
||||||
|
"name":"iframe_api",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"iframe.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteAllowApi",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":6,
|
||||||
|
"nextobjectid":1,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.3.3",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.2,
|
||||||
|
"width":10
|
||||||
|
}
|
79
maps/tests/script.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
console.log('SCRIPT LAUNCHED');
|
||||||
|
//WA.sendChatMessage('Hi, my name is Poly and I repeat what you say!', 'Poly Parrot');
|
||||||
|
var isFirstTimeTuto = false;
|
||||||
|
var textFirstPopup = 'Hey ! This is how to open start a discussion with someone ! You can be 4 max in a booble';
|
||||||
|
var textSecondPopup = 'You can also use the chat to communicate ! ';
|
||||||
|
var targetObjectTutoBubble ='tutoBobble';
|
||||||
|
var targetObjectTutoChat ='tutoChat';
|
||||||
|
var popUpExplanation = undefined;
|
||||||
|
function launchTuto (){
|
||||||
|
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
|
||||||
|
{
|
||||||
|
label: "Next",
|
||||||
|
className: "popUpElement",
|
||||||
|
callback: (popup) => {
|
||||||
|
popup.close();
|
||||||
|
|
||||||
|
WA.openPopup(targetObjectTutoChat, textSecondPopup, [
|
||||||
|
{
|
||||||
|
label: "Open Chat",
|
||||||
|
className: "popUpElement",
|
||||||
|
callback: (popup1) => {
|
||||||
|
WA.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
|
||||||
|
popup1.close();
|
||||||
|
WA.restorePlayerControl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
WA.disablePlayerControl();
|
||||||
|
|
||||||
|
}
|
||||||
|
WA.onChatMessage((message => {
|
||||||
|
console.log('CHAT MESSAGE RECEIVED BY SCRIPT');
|
||||||
|
WA.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot');
|
||||||
|
}));
|
||||||
|
|
||||||
|
WA.onEnterZone('myTrigger', () => {
|
||||||
|
WA.sendChatMessage("Don't step on my carpet!", 'Poly Parrot');
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onLeaveZone('popupZone', () => {
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onEnterZone('notExist', () => {
|
||||||
|
WA.sendChatMessage("YOU SHOULD NEVER SEE THIS", 'Poly Parrot');
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onEnterZone('popupZone', () => {
|
||||||
|
WA.displayBubble();
|
||||||
|
if (!isFirstTimeTuto) {
|
||||||
|
isFirstTimeTuto = true;
|
||||||
|
launchTuto();
|
||||||
|
}
|
||||||
|
else popUpExplanation = WA.openPopup(targetObjectTutoChat,'Do you want to review the explanation ? ', [
|
||||||
|
{
|
||||||
|
label: "No",
|
||||||
|
className: "popUpElementReviewexplanation",
|
||||||
|
callback: (popup) => {
|
||||||
|
popup.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Yes",
|
||||||
|
className: "popUpElementReviewexplanation",
|
||||||
|
callback: (popup) => {
|
||||||
|
popup.close();
|
||||||
|
launchTuto();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
WA.onLeaveZone('popupZone', () => {
|
||||||
|
if (popUpExplanation !== undefined) popUpExplanation.close();
|
||||||
|
WA.removeBubble();
|
||||||
|
})
|
135
maps/tests/script_api.json
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"editorsettings":
|
||||||
|
{
|
||||||
|
"export":
|
||||||
|
{
|
||||||
|
"target":"."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height":10,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":10,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":6,
|
||||||
|
"name":"triggerZone",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"zone",
|
||||||
|
"type":"string",
|
||||||
|
"value":"myTrigger"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":7,
|
||||||
|
"name":"popupZone",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"zone",
|
||||||
|
"type":"string",
|
||||||
|
"value":"popupZone"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":147.135497146101,
|
||||||
|
"id":1,
|
||||||
|
"name":"myPopup2",
|
||||||
|
"rotation":0,
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":104.442827410047,
|
||||||
|
"x":142.817125079855,
|
||||||
|
"y":147.448134926559
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":132.434722966794,
|
||||||
|
"id":2,
|
||||||
|
"name":"myPopup1",
|
||||||
|
"rotation":0,
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":125.735549178518,
|
||||||
|
"x":13.649632619596,
|
||||||
|
"y":50.8502491249093
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":8,
|
||||||
|
"nextobjectid":3,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"script.js"
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.4.3",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.4,
|
||||||
|
"width":10
|
||||||
|
}
|
@ -197,6 +197,22 @@ message SendUserMessage{
|
|||||||
string message = 2;
|
string message = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message WorldFullWarningMessage{
|
||||||
|
}
|
||||||
|
message WorldFullWarningToRoomMessage{
|
||||||
|
string roomId = 1;
|
||||||
|
}
|
||||||
|
message RefreshRoomPromptMessage{
|
||||||
|
string roomId = 1;
|
||||||
|
}
|
||||||
|
message RefreshRoomMessage{
|
||||||
|
string roomId = 1;
|
||||||
|
int32 versionNumber = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorldFullMessage{
|
||||||
|
}
|
||||||
|
|
||||||
message BanUserMessage{
|
message BanUserMessage{
|
||||||
string type = 1;
|
string type = 1;
|
||||||
string message = 2;
|
string message = 2;
|
||||||
@ -218,6 +234,9 @@ message ServerToClientMessage {
|
|||||||
SendUserMessage sendUserMessage = 12;
|
SendUserMessage sendUserMessage = 12;
|
||||||
BanUserMessage banUserMessage = 13;
|
BanUserMessage banUserMessage = 13;
|
||||||
AdminRoomMessage adminRoomMessage = 14;
|
AdminRoomMessage adminRoomMessage = 14;
|
||||||
|
WorldFullWarningMessage worldFullWarningMessage = 15;
|
||||||
|
WorldFullMessage worldFullMessage = 16;
|
||||||
|
RefreshRoomMessage refreshRoomMessage = 17;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,4 +402,6 @@ service RoomManager {
|
|||||||
rpc sendGlobalAdminMessage(AdminGlobalMessage) returns (EmptyMessage);
|
rpc sendGlobalAdminMessage(AdminGlobalMessage) returns (EmptyMessage);
|
||||||
rpc ban(BanMessage) returns (EmptyMessage);
|
rpc ban(BanMessage) returns (EmptyMessage);
|
||||||
rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage);
|
rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage);
|
||||||
|
rpc sendWorldFullWarningToRoom(WorldFullWarningToRoomMessage) returns (EmptyMessage);
|
||||||
|
rpc sendRefreshRoomPrompt(RefreshRoomPromptMessage) returns (EmptyMessage);
|
||||||
}
|
}
|
||||||
|
@ -4155,9 +4155,9 @@ xtend@~4.0.1:
|
|||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
y18n@^3.2.0:
|
y18n@^3.2.0:
|
||||||
version "3.2.1"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
|
@ -2,7 +2,7 @@ import {BaseController} from "./BaseController";
|
|||||||
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
||||||
import {apiClientRepository} from "../Services/ApiClientRepository";
|
import {apiClientRepository} from "../Services/ApiClientRepository";
|
||||||
import {AdminRoomMessage} from "../Messages/generated/messages_pb";
|
import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
|
|
||||||
export class AdminController extends BaseController{
|
export class AdminController extends BaseController{
|
||||||
@ -11,6 +11,56 @@ export class AdminController extends BaseController{
|
|||||||
super();
|
super();
|
||||||
this.App = App;
|
this.App = App;
|
||||||
this.receiveGlobalMessagePrompt();
|
this.receiveGlobalMessagePrompt();
|
||||||
|
this.receiveRoomEditionPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveRoomEditionPrompt() {
|
||||||
|
this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
res.onAborted(() => {
|
||||||
|
console.warn('/message request was aborted');
|
||||||
|
})
|
||||||
|
|
||||||
|
const token = req.getHeader('admin-token');
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
if (token !== ADMIN_API_TOKEN) {
|
||||||
|
console.error('Admin access refused for token: '+token)
|
||||||
|
res.writeStatus("401 Unauthorized").end('Incorrect token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof body.roomId !== 'string') {
|
||||||
|
throw 'Incorrect roomId parameter'
|
||||||
|
}
|
||||||
|
const roomId: string = body.roomId;
|
||||||
|
|
||||||
|
await apiClientRepository.getClient(roomId).then((roomClient) =>{
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
const roomMessage = new RefreshRoomPromptMessage();
|
||||||
|
roomMessage.setRoomid(roomId);
|
||||||
|
|
||||||
|
roomClient.sendRefreshRoomPrompt(roomMessage, (err) => {
|
||||||
|
err ? rej(err) : res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.errorToResponse(err, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeStatus("200");
|
||||||
|
res.end('ok');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveGlobalMessagePrompt() {
|
receiveGlobalMessagePrompt() {
|
||||||
@ -40,22 +90,36 @@ export class AdminController extends BaseController{
|
|||||||
if (typeof body.text !== 'string') {
|
if (typeof body.text !== 'string') {
|
||||||
throw 'Incorrect text parameter'
|
throw 'Incorrect text parameter'
|
||||||
}
|
}
|
||||||
|
if (body.type !== 'capacity' && body.type !== 'message') {
|
||||||
|
throw 'Incorrect type parameter'
|
||||||
|
}
|
||||||
if (!body.targets || typeof body.targets !== 'object') {
|
if (!body.targets || typeof body.targets !== 'object') {
|
||||||
throw 'Incorrect targets parameter'
|
throw 'Incorrect targets parameter'
|
||||||
}
|
}
|
||||||
const text: string = body.text;
|
const text: string = body.text;
|
||||||
|
const type: string = body.type;
|
||||||
const targets: string[] = body.targets;
|
const targets: string[] = body.targets;
|
||||||
|
|
||||||
await Promise.all(targets.map((roomId) => {
|
await Promise.all(targets.map((roomId) => {
|
||||||
return apiClientRepository.getClient(roomId).then((roomClient) =>{
|
return apiClientRepository.getClient(roomId).then((roomClient) =>{
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const roomMessage = new AdminRoomMessage();
|
if (type === 'message') {
|
||||||
roomMessage.setMessage(text);
|
const roomMessage = new AdminRoomMessage();
|
||||||
roomMessage.setRoomid(roomId);
|
roomMessage.setMessage(text);
|
||||||
|
roomMessage.setRoomid(roomId);
|
||||||
|
|
||||||
|
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
|
||||||
|
err ? rej(err) : res();
|
||||||
|
});
|
||||||
|
} else if (type === 'capacity') {
|
||||||
|
const roomMessage = new WorldFullWarningToRoomMessage();
|
||||||
|
roomMessage.setRoomid(roomId);
|
||||||
|
|
||||||
|
roomClient.sendWorldFullWarningToRoom(roomMessage, (err) => {
|
||||||
|
err ? rej(err) : res();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
roomClient.sendAdminMessageToRoom(roomMessage, (err) => {
|
|
||||||
err ? rej(err) : res();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
import {HttpResponse} from "uWebSockets.js";
|
||||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
|
|
||||||
export class BaseController {
|
export class BaseController {
|
||||||
@ -14,7 +13,20 @@ export class BaseController {
|
|||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
protected errorToResponse(e: any, res: HttpResponse): void {
|
protected errorToResponse(e: any, res: HttpResponse): void {
|
||||||
console.error("An error happened", e);
|
if (e && e.message) {
|
||||||
|
let url = e?.config?.url;
|
||||||
|
if (url !== undefined) {
|
||||||
|
url = ' for URL: '+url;
|
||||||
|
} else {
|
||||||
|
url = '';
|
||||||
|
}
|
||||||
|
console.error('ERROR: '+e.message+url);
|
||||||
|
} else if (typeof(e) === 'string') {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (e.stack) {
|
||||||
|
console.error(e.stack);
|
||||||
|
}
|
||||||
if (e.response) {
|
if (e.response) {
|
||||||
res.writeStatus(e.response.status+" "+e.response.statusText);
|
res.writeStatus(e.response.status+" "+e.response.statusText);
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
|
@ -190,10 +190,10 @@ export class IoSocketController {
|
|||||||
memberMessages = userData.messages;
|
memberMessages = userData.messages;
|
||||||
memberTags = userData.tags;
|
memberTags = userData.tags;
|
||||||
memberTextures = userData.textures;
|
memberTextures = userData.textures;
|
||||||
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
||||||
throw new Error('No correct tags')
|
throw new Error('No correct tags')
|
||||||
}
|
}
|
||||||
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
|
if (!room.public && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
|
||||||
throw new Error('No correct member')
|
throw new Error('No correct member')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -258,12 +258,12 @@ export class IoSocketController {
|
|||||||
/* Handlers */
|
/* Handlers */
|
||||||
open: (ws) => {
|
open: (ws) => {
|
||||||
if(ws.rejected === true) {
|
if(ws.rejected === true) {
|
||||||
emitError(ws, 'World is full');
|
socketManager.emitWorldFullMessage(ws);
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's join the room
|
// Let's join the room
|
||||||
const client = this.initClient(ws); //todo: into the upgrade instead?
|
const client = this.initClient(ws);
|
||||||
socketManager.handleJoinRoom(client);
|
socketManager.handleJoinRoom(client);
|
||||||
|
|
||||||
//get data information and show messages
|
//get data information and show messages
|
||||||
|