Merge pull request #1125 from thecodingmachine/develop
Deploy 2021-06-04
@ -10,6 +10,8 @@ START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
|
|||||||
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
|
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
|
||||||
# Keep empty if you are sharing hard coded / clear text credentials.
|
# Keep empty if you are sharing hard coded / clear text credentials.
|
||||||
TURN_STATIC_AUTH_SECRET=
|
TURN_STATIC_AUTH_SECRET=
|
||||||
|
DISABLE_NOTIFICATIONS=true
|
||||||
|
SKIP_RENDER_OPTIMIZATIONS=false
|
||||||
|
|
||||||
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
||||||
ACME_EMAIL=
|
ACME_EMAIL=
|
||||||
|
2
.github/workflows/build-and-deploy.yml
vendored
@ -2,7 +2,7 @@ name: Build, push and deploy Docker image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master, develop]
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
2
.github/workflows/continuous_integration.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: yarn run build
|
run: yarn run build
|
||||||
env:
|
env:
|
||||||
API_URL: "localhost:8080"
|
PUSHER_URL: "//localhost:8080"
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Lint"
|
- name: "Lint"
|
||||||
|
42
CHANGELOG.md
@ -1,4 +1,44 @@
|
|||||||
## Version 1.3.0 - in dev
|
## Version 1.3.9 - in dev
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
- Scripting API:
|
||||||
|
- Changed function names: `restorePlayerControl` => `restorePlayerControls`, `disablePlayerControl` => `disablePlayerControls`.
|
||||||
|
Please keep in mind that the scripting API is still experimental. Some breaking changes can occur in it until we mark it as stable.
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
|
||||||
|
- Added the emote feature to WorkAdventure. (@Kharhamel, @Tabascoeye)
|
||||||
|
- The emote menu can be opened by clicking on your character.
|
||||||
|
- Clicking on one of its element will close the menu and play an emote above your character.
|
||||||
|
- This emote can be seen by other players.
|
||||||
|
- Player names were improved. (@Kharhamel)
|
||||||
|
- We now create a GameObject.Text instead of GameObject.BitmapText
|
||||||
|
- now use the 'Press Start 2P' font family and added an outline
|
||||||
|
- As a result, we can now allow non-standard letters like french accents or chinese characters!
|
||||||
|
|
||||||
|
- Added the contact card feature. (@Kharhamel)
|
||||||
|
- Click on another player to see its contact info.
|
||||||
|
- Premium-only feature unfortunately. I need to find a way to make it available for all.
|
||||||
|
- If no contact data is found (either because the user is anonymous or because no admin backend), display an error card.
|
||||||
|
|
||||||
|
- Mobile support has been improved
|
||||||
|
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
|
||||||
|
- Mouse wheel support to zoom in / out
|
||||||
|
- Pinch support on mobile to zoom in / out
|
||||||
|
- Improved virtual joystick size (adapts to the zoom level)
|
||||||
|
- Redesigned intermediate scenes
|
||||||
|
- Redesigned Select Companion scene
|
||||||
|
- Redesigned Enter Your Name scene
|
||||||
|
- Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use
|
||||||
|
- New scripting API features:
|
||||||
|
- Use `WA.loadSound(): Sound` to load / play / stop a sound
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Pinch gesture does no longer move the character
|
||||||
|
|
||||||
|
## Version 1.3.0
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ import {PointInterface} from "./Websocket/PointInterface";
|
|||||||
import {Group} from "./Group";
|
import {Group} from "./Group";
|
||||||
import {User, UserSocket} from "./User";
|
import {User, UserSocket} from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import {PositionInterface} from "_Model/PositionInterface";
|
||||||
import {EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
|
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
|
||||||
import {PositionNotifier} from "./PositionNotifier";
|
import {PositionNotifier} from "./PositionNotifier";
|
||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import {arrayIntersect} from "../Services/ArrayHelper";
|
||||||
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
|
import {EmoteEventMessage, JoinRoomMessage} from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
import {ZoneSocket} from "src/RoomManager";
|
import {ZoneSocket} from "src/RoomManager";
|
||||||
import {Admin} from "../Model/Admin";
|
import {Admin} from "../Model/Admin";
|
||||||
@ -51,8 +51,9 @@ export class GameRoom {
|
|||||||
groupRadius: number,
|
groupRadius: number,
|
||||||
onEnters: EntersCallback,
|
onEnters: EntersCallback,
|
||||||
onMoves: MovesCallback,
|
onMoves: MovesCallback,
|
||||||
onLeaves: LeavesCallback)
|
onLeaves: LeavesCallback,
|
||||||
{
|
onEmote: EmoteCallback,
|
||||||
|
) {
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
|
|
||||||
if (isRoomAnonymous(roomId)) {
|
if (isRoomAnonymous(roomId)) {
|
||||||
@ -74,7 +75,7 @@ export class GameRoom {
|
|||||||
this.minDistance = minDistance;
|
this.minDistance = minDistance;
|
||||||
this.groupRadius = groupRadius;
|
this.groupRadius = groupRadius;
|
||||||
// A zone is 10 sprites wide.
|
// A zone is 10 sprites wide.
|
||||||
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves);
|
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves, onEmote);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGroups(): Group[] {
|
public getGroups(): Group[] {
|
||||||
@ -88,7 +89,10 @@ export class GameRoom {
|
|||||||
public getUserByUuid(uuid: string): User|undefined {
|
public getUserByUuid(uuid: string): User|undefined {
|
||||||
return this.usersByUuid.get(uuid);
|
return this.usersByUuid.get(uuid);
|
||||||
}
|
}
|
||||||
|
public getUserById(id: number): User|undefined {
|
||||||
|
return this.users.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
||||||
const positionMessage = joinRoomMessage.getPositionmessage();
|
const positionMessage = joinRoomMessage.getPositionmessage();
|
||||||
if (positionMessage === undefined) {
|
if (positionMessage === undefined) {
|
||||||
@ -325,4 +329,8 @@ export class GameRoom {
|
|||||||
this.versionNumber++
|
this.versionNumber++
|
||||||
return this.versionNumber;
|
return this.versionNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(user: User, emoteEventMessage: EmoteEventMessage) {
|
||||||
|
this.positionNotifier.emitEmoteEvent(user, emoteEventMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
||||||
* number of players around the current player.
|
* number of players around the current player.
|
||||||
*/
|
*/
|
||||||
import {EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
|
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import {PositionInterface} from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import {ZoneSocket} from "../RoomManager";
|
||||||
|
import {User} from "_Model/User";
|
||||||
|
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
interface ZoneDescriptor {
|
interface ZoneDescriptor {
|
||||||
i: number;
|
i: number;
|
||||||
@ -24,7 +26,7 @@ export class PositionNotifier {
|
|||||||
|
|
||||||
private zones: Zone[][] = [];
|
private zones: Zone[][] = [];
|
||||||
|
|
||||||
constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback) {
|
constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback, private onEmote: EmoteCallback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
||||||
@ -77,7 +79,7 @@ export class PositionNotifier {
|
|||||||
|
|
||||||
let zone = this.zones[j][i];
|
let zone = this.zones[j][i];
|
||||||
if (zone === undefined) {
|
if (zone === undefined) {
|
||||||
zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves, i, j);
|
zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves, this.onEmote, i, j);
|
||||||
this.zones[j][i] = zone;
|
this.zones[j][i] = zone;
|
||||||
}
|
}
|
||||||
return zone;
|
return zone;
|
||||||
@ -93,4 +95,11 @@ export class PositionNotifier {
|
|||||||
const zone = this.getZone(x, y);
|
const zone = this.getZone(x, y);
|
||||||
zone.removeListener(call);
|
zone.removeListener(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(user: User, emoteEventMessage: EmoteEventMessage) {
|
||||||
|
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
|
||||||
|
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
||||||
|
zone.emitEmoteEvent(emoteEventMessage);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,21 +3,19 @@ import {PositionInterface} from "_Model/PositionInterface";
|
|||||||
import {Movable} from "./Movable";
|
import {Movable} from "./Movable";
|
||||||
import {Group} from "./Group";
|
import {Group} from "./Group";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import {ZoneSocket} from "../RoomManager";
|
||||||
|
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void;
|
export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void;
|
||||||
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
||||||
export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void;
|
export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void;
|
||||||
|
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
private things: Set<Movable> = new Set<Movable>();
|
private things: Set<Movable> = new Set<Movable>();
|
||||||
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
||||||
|
|
||||||
/**
|
|
||||||
* @param x For debugging purpose only
|
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, public readonly x: number, public readonly y: number) { }
|
||||||
* @param y For debugging purpose only
|
|
||||||
*/
|
|
||||||
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, public readonly x: number, public readonly y: number) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A user/thing leaves the zone
|
* A user/thing leaves the zone
|
||||||
@ -41,9 +39,7 @@ export class Zone {
|
|||||||
*/
|
*/
|
||||||
private notifyLeft(thing: Movable, newZone: Zone|null) {
|
private notifyLeft(thing: Movable, newZone: Zone|null) {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
//if (listener !== thing && (newZone === null || !listener.listenedZones.has(newZone))) {
|
this.onLeaves(thing, newZone, listener);
|
||||||
this.onLeaves(thing, newZone, listener);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,15 +53,6 @@ export class Zone {
|
|||||||
*/
|
*/
|
||||||
private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
|
private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
|
|
||||||
/*if (listener === thing) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (oldZone === null || !listener.listenedZones.has(oldZone)) {
|
|
||||||
this.onEnters(thing, listener);
|
|
||||||
} else {
|
|
||||||
this.onMoves(thing, position, listener);
|
|
||||||
}*/
|
|
||||||
this.onEnters(thing, oldZone, listener);
|
this.onEnters(thing, oldZone, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,28 +72,6 @@ export class Zone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public startListening(listener: User): void {
|
|
||||||
for (const thing of this.things) {
|
|
||||||
if (thing !== listener) {
|
|
||||||
this.onEnters(thing, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners.add(listener);
|
|
||||||
listener.listenedZones.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public stopListening(listener: User): void {
|
|
||||||
for (const thing of this.things) {
|
|
||||||
if (thing !== listener) {
|
|
||||||
this.onLeaves(thing, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners.delete(listener);
|
|
||||||
listener.listenedZones.delete(this);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public getThings(): Set<Movable> {
|
public getThings(): Set<Movable> {
|
||||||
return this.things;
|
return this.things;
|
||||||
}
|
}
|
||||||
@ -119,4 +84,11 @@ export class Zone {
|
|||||||
public removeListener(socket: ZoneSocket): void {
|
public removeListener(socket: ZoneSocket): void {
|
||||||
this.listeners.delete(socket);
|
this.listeners.delete(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(emoteEventMessage: EmoteEventMessage) {
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
this.onEmote(emoteEventMessage, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,13 @@ import {
|
|||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
AdminRoomMessage,
|
AdminRoomMessage,
|
||||||
BanMessage,
|
BanMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
|
QueryJitsiJwtMessage, RefreshRoomPromptMessage, RequestVisitCardMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
@ -71,6 +72,10 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()){
|
||||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||||
|
} else if (message.hasEmotepromptmessage()){
|
||||||
|
socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage);
|
||||||
|
} else if (message.hasRequestvisitcardmessage()) {
|
||||||
|
socketManager.handleRequestVisitCardMessage(room, user, message.getRequestvisitcardmessage() as RequestVisitCardMessage);
|
||||||
}else if (message.hasSendusermessage()) {
|
}else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
if(sendUserMessage !== undefined) {
|
if(sendUserMessage !== undefined) {
|
||||||
|
22
back/src/Services/AdminApi.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
import Axios from "axios";
|
||||||
|
|
||||||
|
|
||||||
|
class AdminApi {
|
||||||
|
|
||||||
|
fetchVisitCardUrl(membershipUuid: string): Promise<string> {
|
||||||
|
if (ADMIN_API_URL) {
|
||||||
|
return Axios.get(ADMIN_API_URL + '/api/membership/'+membershipUuid,
|
||||||
|
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
|
||||||
|
).then((res) => {
|
||||||
|
return res.data;
|
||||||
|
}).catch(() => {
|
||||||
|
return 'INVALID';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve('INVALID')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const adminApi = new AdminApi();
|
@ -26,7 +26,8 @@ import {
|
|||||||
GroupLeftZoneMessage,
|
GroupLeftZoneMessage,
|
||||||
WorldFullWarningMessage,
|
WorldFullWarningMessage,
|
||||||
UserLeftZoneMessage,
|
UserLeftZoneMessage,
|
||||||
BanUserMessage, RefreshRoomMessage,
|
EmoteEventMessage,
|
||||||
|
BanUserMessage, RefreshRoomMessage, EmotePromptMessage, RequestVisitCardMessage, VisitCardMessage,
|
||||||
} 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";
|
||||||
@ -50,6 +51,7 @@ import {Zone} from "_Model/Zone";
|
|||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
import {Admin} from "_Model/Admin";
|
import {Admin} from "_Model/Admin";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import {adminApi} from "./AdminApi";
|
||||||
|
|
||||||
|
|
||||||
const debug = Debug('sockermanager');
|
const debug = Debug('sockermanager');
|
||||||
@ -67,6 +69,7 @@ export class SocketManager {
|
|||||||
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
||||||
gaugeManager.incNbClientPerRoomGauge(roomId);
|
gaugeManager.incNbClientPerRoomGauge(roomId);
|
||||||
});
|
});
|
||||||
@ -263,7 +266,8 @@ export class SocketManager {
|
|||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener),
|
(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener),
|
||||||
(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),
|
||||||
|
(emoteEventMessage:EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener),
|
||||||
);
|
);
|
||||||
gaugeManager.incNbRoomGauge();
|
gaugeManager.incNbRoomGauge();
|
||||||
this.rooms.set(roomId, world);
|
this.rooms.set(roomId, world);
|
||||||
@ -339,6 +343,14 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
|
||||||
|
const subMessage = new SubToPusherMessage();
|
||||||
|
subMessage.setEmoteeventmessage(emoteEventMessage);
|
||||||
|
|
||||||
|
emitZoneMessage(subMessage, client);
|
||||||
|
}
|
||||||
|
|
||||||
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void {
|
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void {
|
||||||
const position = group.getPosition();
|
const position = group.getPosition();
|
||||||
const pointMessage = new PointMessage();
|
const pointMessage = new PointMessage();
|
||||||
@ -751,6 +763,28 @@ export class SocketManager {
|
|||||||
recipient.socket.write(clientMessage);
|
recipient.socket.write(clientMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmoteEventMessage(room: GameRoom, user: User, emotePromptMessage: EmotePromptMessage) {
|
||||||
|
const emoteEventMessage = new EmoteEventMessage();
|
||||||
|
emoteEventMessage.setEmote(emotePromptMessage.getEmote());
|
||||||
|
emoteEventMessage.setActoruserid(user.id);
|
||||||
|
room.emitEmoteEvent(user, emoteEventMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRequestVisitCardMessage(room: GameRoom, user: User, requestvisitcardmessage: RequestVisitCardMessage): Promise<void> {
|
||||||
|
const targetUser = room.getUserById(requestvisitcardmessage.getTargetuserid());
|
||||||
|
if (!targetUser) {
|
||||||
|
throw 'Could not find user for id '+requestvisitcardmessage.getTargetuserid();
|
||||||
|
}
|
||||||
|
const url = await adminApi.fetchVisitCardUrl(targetUser.uuid);
|
||||||
|
|
||||||
|
const visitCardMessage = new VisitCardMessage();
|
||||||
|
visitCardMessage.setUrl(url);
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setVisitcardmessage(visitCardMessage);
|
||||||
|
|
||||||
|
user.socket.write(clientMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -5,6 +5,7 @@ import {Group} from "../src/Model/Group";
|
|||||||
import {User, UserSocket} from "_Model/User";
|
import {User, UserSocket} from "_Model/User";
|
||||||
import {JoinRoomMessage, PositionMessage} from "../src/Messages/generated/messages_pb";
|
import {JoinRoomMessage, PositionMessage} from "../src/Messages/generated/messages_pb";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
|
import {EmoteCallback} from "_Model/Zone";
|
||||||
|
|
||||||
function createMockUser(userId: number): User {
|
function createMockUser(userId: number): User {
|
||||||
return {
|
return {
|
||||||
@ -33,6 +34,8 @@ function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMess
|
|||||||
return joinRoomMessage;
|
return joinRoomMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emote: EmoteCallback = (emoteEventMessage, listener): void => {}
|
||||||
|
|
||||||
describe("GameRoom", () => {
|
describe("GameRoom", () => {
|
||||||
it("should connect user1 and user2", () => {
|
it("should connect user1 and user2", () => {
|
||||||
let connectCalledNumber: number = 0;
|
let connectCalledNumber: number = 0;
|
||||||
@ -43,7 +46,8 @@ describe("GameRoom", () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
|
|
||||||
|
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -72,7 +76,7 @@ describe("GameRoom", () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
|
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
||||||
|
|
||||||
@ -101,7 +105,7 @@ describe("GameRoom", () => {
|
|||||||
disconnectCallNumber++;
|
disconnectCallNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
|
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ describe("PositionNotifier", () => {
|
|||||||
moveTriggered = true;
|
moveTriggered = true;
|
||||||
}, (thing: Movable) => {
|
}, (thing: Movable) => {
|
||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
});
|
}, () => {});
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||||
x: 500,
|
x: 500,
|
||||||
@ -98,7 +98,7 @@ describe("PositionNotifier", () => {
|
|||||||
moveTriggered = true;
|
moveTriggered = true;
|
||||||
}, (thing: Movable) => {
|
}, (thing: Movable) => {
|
||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
});
|
}, () => {});
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||||
x: 500,
|
x: 500,
|
||||||
|
@ -1251,9 +1251,9 @@ has-values@^1.0.0:
|
|||||||
kind-of "^4.0.0"
|
kind-of "^4.0.0"
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.8"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||||
|
|
||||||
http-errors@1.7.2:
|
http-errors@1.7.2:
|
||||||
version "1.7.2"
|
version "1.7.2"
|
||||||
@ -1704,9 +1704,9 @@ lodash.once@^4.0.0:
|
|||||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||||
|
|
||||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||||
version "4.17.20"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
long@~3:
|
long@~3:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
|
6
benchmark/package-lock.json
generated
@ -230,9 +230,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
|
||||||
},
|
},
|
||||||
"indent-string": {
|
"indent-string": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
@ -169,8 +169,8 @@ graceful-fs@^4.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.8"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
|
|
||||||
indent-string@^2.1.0:
|
indent-string@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
|
@ -33,6 +33,8 @@ services:
|
|||||||
STARTUP_COMMAND_1: ./templater.sh
|
STARTUP_COMMAND_1: ./templater.sh
|
||||||
STARTUP_COMMAND_2: yarn install
|
STARTUP_COMMAND_2: yarn install
|
||||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||||
|
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
|
||||||
|
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
|
||||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
# 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
|
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||||
TURN_USER: ""
|
TURN_USER: ""
|
||||||
@ -153,23 +155,6 @@ services:
|
|||||||
- "traefik.http.routers.uploader-ssl.tls=true"
|
- "traefik.http.routers.uploader-ssl.tls=true"
|
||||||
- "traefik.http.routers.uploader-ssl.service=uploader"
|
- "traefik.http.routers.uploader-ssl.service=uploader"
|
||||||
|
|
||||||
website:
|
|
||||||
image: thecodingmachine/nodejs:12-apache
|
|
||||||
environment:
|
|
||||||
STARTUP_COMMAND_1: npm install
|
|
||||||
STARTUP_COMMAND_2: npm run watch &
|
|
||||||
APACHE_DOCUMENT_ROOT: dist/
|
|
||||||
volumes:
|
|
||||||
- ./website:/var/www/html
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.website.rule=Host(`workadventure.localhost`)"
|
|
||||||
- "traefik.http.routers.website.entryPoints=web"
|
|
||||||
- "traefik.http.services.website.loadbalancer.server.port=80"
|
|
||||||
- "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)"
|
|
||||||
- "traefik.http.routers.website-ssl.entryPoints=websecure"
|
|
||||||
- "traefik.http.routers.website-ssl.tls=true"
|
|
||||||
- "traefik.http.routers.website-ssl.service=website"
|
|
||||||
|
|
||||||
messages:
|
messages:
|
||||||
#image: thecodingmachine/nodejs:14
|
#image: thecodingmachine/nodejs:14
|
||||||
image: thecodingmachine/workadventure-back-base:latest
|
image: thecodingmachine/workadventure-back-base:latest
|
||||||
|
@ -33,6 +33,8 @@ services:
|
|||||||
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"
|
||||||
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
||||||
|
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
|
||||||
|
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
|
||||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
# 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
|
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||||
TURN_USER: ""
|
TURN_USER: ""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{.alert.alert-danger}
|
{.alert.alert-danger style="width:80%"}
|
||||||
This feature is "_experimental_". We may apply changes in the near future to the way it works when we gather some feedback.
|
This feature is "_experimental_". We may apply changes in the near future to the way it works when we gather some feedback.
|
||||||
|
|
||||||
{.section-title.accent.text-primary}
|
{.section-title.accent.text-primary}
|
||||||
|
@ -25,6 +25,15 @@
|
|||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "error"
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
|
||||||
|
// TODO: remove those ignored rules and write a stronger code!
|
||||||
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/restrict-template-expressions": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
front/dist/index.tmpl.html
vendored
@ -29,7 +29,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<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" />
|
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
<title>WorkAdventure</title>
|
<title>WorkAdventure</title>
|
||||||
@ -38,6 +37,8 @@
|
|||||||
<div class="main-container" id="main-container">
|
<div class="main-container" id="main-container">
|
||||||
<!-- Create the editor container -->
|
<!-- Create the editor container -->
|
||||||
<div id="game" class="game">
|
<div id="game" class="game">
|
||||||
|
<div id="svelte-overlay">
|
||||||
|
</div>
|
||||||
<div id="game-overlay" class="game-overlay">
|
<div id="game-overlay" class="game-overlay">
|
||||||
<div id="main-section" class="main-section">
|
<div id="main-section" class="main-section">
|
||||||
</div>
|
</div>
|
||||||
@ -45,33 +46,6 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
<div id="activeCam" class="activeCam">
|
|
||||||
<div id="div-myCamVideo" class="video-container">
|
|
||||||
<video id="myCamVideo" autoplay muted></video>
|
|
||||||
<div id="mySoundMeter" class="sound-progress">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="btn-cam-action">
|
|
||||||
<div id="btn-monitor" class="btn-monitor">
|
|
||||||
<img id="monitor" src="resources/logos/monitor.svg">
|
|
||||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
|
||||||
</div>
|
|
||||||
<div id="btn-video" class="btn-video">
|
|
||||||
<img id="cinema" src="resources/logos/cinema.svg">
|
|
||||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
|
||||||
</div>
|
|
||||||
<div id="btn-micro" class="btn-micro">
|
|
||||||
<img id="microphone" src="resources/logos/microphone.svg">
|
|
||||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="cowebsite" class="cowebsite hidden">
|
<div id="cowebsite" class="cowebsite hidden">
|
||||||
@ -106,30 +80,17 @@
|
|||||||
</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="audioplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
|
||||||
reduce in conversations
|
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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="audio-playing">
|
|
||||||
<img src="/resources/logos/megaphone.svg" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||||
</div>
|
</div>
|
||||||
<div id="webRtcSetup" class="webrtcsetup">
|
|
||||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
|
||||||
<video id="myCamVideoSetup" autoplay muted></video>
|
|
||||||
</div>
|
|
||||||
<audio id="audio-webrtc-in">
|
|
||||||
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
|
|
||||||
</audio>
|
|
||||||
<audio id="audio-webrtc-out">
|
|
||||||
<source src="/resources/objects/webrtc-out.mp3" type="audio/mp3">
|
|
||||||
</audio>
|
|
||||||
<audio id="report-message">
|
<audio id="report-message">
|
||||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
||||||
</audio>
|
</audio>
|
||||||
|
BIN
front/dist/resources/emotes/clap-emote.png
vendored
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
front/dist/resources/emotes/hand-emote.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
front/dist/resources/emotes/heart-emote.png
vendored
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
front/dist/resources/emotes/thanks-emote.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
front/dist/resources/emotes/thumb-down-emote.png
vendored
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
front/dist/resources/emotes/thumb-up-emote.png
vendored
Normal file
After Width: | Height: | Size: 8.6 KiB |
5
front/dist/resources/fonts/fonts.css
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/*This file is a workaround to allow phaser to load directly this font */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2');
|
||||||
|
}
|
160
front/dist/resources/html/CustomCharacterScene.html
vendored
@ -1,160 +0,0 @@
|
|||||||
<style>
|
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#customizeScene {
|
|
||||||
background: #0000;
|
|
||||||
/*border: 1px solid #ebeeee;*/
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px auto 0;
|
|
||||||
color: #ebeeee;
|
|
||||||
width: 42vw;
|
|
||||||
height: 48vh;
|
|
||||||
/*max-width: 300px;
|
|
||||||
max-height: 48vh;*/
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#customizeScene h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#customizeScene input {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#customizeScene section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#customizeScene section.action {
|
|
||||||
text-align: center;
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
top: 100%;
|
|
||||||
}
|
|
||||||
#customizeScene section.action.action-move {
|
|
||||||
top: 55%;
|
|
||||||
}
|
|
||||||
#customizeScene button {
|
|
||||||
margin: 2px 10px;
|
|
||||||
background-color: black;;
|
|
||||||
color: #ebeeee;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
#customizeScene button#customizeSceneFormCancel {
|
|
||||||
background-color: #aca6a600;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#customizeScene section h6,
|
|
||||||
#customizeScene section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#customizeScene section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#customizeScene section a{
|
|
||||||
font-size: 14px;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #ebeeee;
|
|
||||||
}
|
|
||||||
#customizeScene section a:hover{
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
#customizeScene section p{
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 10px 10px;
|
|
||||||
}
|
|
||||||
#customizeScene section p.err{
|
|
||||||
color: red;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#customizeScene section p.info{
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#customizeScene section input#customizeSceneLink{
|
|
||||||
background-color: #a1a3a3;
|
|
||||||
}
|
|
||||||
#customizeScene section button.customizeSceneButton{
|
|
||||||
position: absolute;
|
|
||||||
margin: 0;
|
|
||||||
top: -8vh;
|
|
||||||
font-size: 10px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonLeft{
|
|
||||||
left: 0vw;
|
|
||||||
}
|
|
||||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonRight{
|
|
||||||
right: 0vw;
|
|
||||||
}
|
|
||||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonUp{
|
|
||||||
left: calc(2vw + 40px);
|
|
||||||
transform: rotate(90deg);
|
|
||||||
margin-top: -20px;
|
|
||||||
}
|
|
||||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonDown{
|
|
||||||
right: calc(2vw + 40px);
|
|
||||||
transform: rotate(90deg);
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
#customizeScene section.action img{
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
#customizeScene section.action a#customizeSceneFormBack img{
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
#customizeScene section.action button#customizeSceneFormSubmit img{
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
#customizeScene {
|
|
||||||
max-width: 160px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media only screen and (max-height: 400px) {
|
|
||||||
#customizeScene section.action {
|
|
||||||
top: 92%;
|
|
||||||
}
|
|
||||||
#customizeScene section.action.action-move {
|
|
||||||
top: 80%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="customizeScene" hidden>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Custom your WOKA</h3>
|
|
||||||
</section>
|
|
||||||
<section class="action action-move">
|
|
||||||
<button class="customizeSceneButton" id="customizeSceneButtonLeft"> < </button>
|
|
||||||
<button class="customizeSceneButton" id="customizeSceneButtonRight"> > </button>
|
|
||||||
<!--<button class="customizeSceneButton" id="customizeSceneButtonUp"> < </button>
|
|
||||||
<button class="customizeSceneButton" id="customizeSceneButtonDown"> > </button>-->
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<a type="submit" id="customizeSceneFormBack">Back <img src="resources/objects/arrow_up.png"/></a>
|
|
||||||
<button type="submit" id="customizeSceneFormSubmit">Next <img src="resources/objects/arrow_up.png"/></button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
129
front/dist/resources/html/EnableCameraScene.html
vendored
@ -1,129 +0,0 @@
|
|||||||
<style>
|
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#enableCameraScene {
|
|
||||||
background: #000000;
|
|
||||||
/*border: 1px solid #ebeeee;*/
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 20px auto 0;
|
|
||||||
color: #ebeeee;
|
|
||||||
max-height: 48vh;
|
|
||||||
width: 42vw;
|
|
||||||
max-width: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#enableCameraScene h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#enableCameraScene input {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#enableCameraScene section.title {
|
|
||||||
position: absolute;
|
|
||||||
top: 1vh;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#enableCameraScene section.action{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 40vh;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#enableCameraScene button {
|
|
||||||
margin: 10px;
|
|
||||||
background-color: black;;
|
|
||||||
color: #ebeeee;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
#enableCameraScene button#enableCameraSceneFormCancel {
|
|
||||||
background-color: #c7c7c700;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#enableCameraScene section h6,
|
|
||||||
#enableCameraScene section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#enableCameraScene section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#enableCameraScene section a{
|
|
||||||
font-size: 8px;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #ebeeee;
|
|
||||||
}
|
|
||||||
#enableCameraScene section a:hover{
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
#enableCameraScene section p{
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 10px 10px;
|
|
||||||
}
|
|
||||||
#enableCameraScene section p.err{
|
|
||||||
color: red;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#enableCameraScene section p.info{
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#enableCameraScene section input#enableCameraSceneLink{
|
|
||||||
background-color: #a1a3a3;
|
|
||||||
}
|
|
||||||
#enableCameraScene section img{
|
|
||||||
width: 160px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
/*@media only screen and (max-width: 800px),
|
|
||||||
only screen and (max-height: 600px) {
|
|
||||||
#enableCameraScene{
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="enableCameraScene" hidden>
|
|
||||||
<!-- FIX me -->
|
|
||||||
<section class="title text-center">
|
|
||||||
<h5>Turn on your camera and microphone</h5>
|
|
||||||
</section>
|
|
||||||
<!--<section class="text-center">
|
|
||||||
<video id="myCamVideoSetup" autoplay muted></video>
|
|
||||||
</section>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Select your camera</h3>
|
|
||||||
<select id="camera">
|
|
||||||
</select>
|
|
||||||
</section>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Select your michrophone</h3>
|
|
||||||
<select id="michrophone">
|
|
||||||
</select>
|
|
||||||
</section>-->
|
|
||||||
<section class="action">
|
|
||||||
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
134
front/dist/resources/html/SelectCompanionScene.html
vendored
@ -1,134 +0,0 @@
|
|||||||
<style>
|
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#selectCompanionScene {
|
|
||||||
background: #0000;
|
|
||||||
/*border: 1px solid #ebeeee;*/
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px auto 0;
|
|
||||||
color: #ebeeee;
|
|
||||||
max-height: 40vh;
|
|
||||||
max-width: 300px;
|
|
||||||
width: 40vw;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#selectCompanionScene h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#selectCompanionScene input {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section.action {
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
margin-top: 20vh;
|
|
||||||
}
|
|
||||||
#selectCompanionScene button {
|
|
||||||
margin: 10px 4px;
|
|
||||||
background-color: black;;
|
|
||||||
color: #ebeeee;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
#selectCompanionScene button#selectCompanionSceneFormCancel {
|
|
||||||
background-color: #aca6a600;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section h6,
|
|
||||||
#selectCompanionScene section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section a{
|
|
||||||
font-size: 14px;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #ebeeee;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section a:hover{
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section p{
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 10px 10px;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section p.err{
|
|
||||||
color: red;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section p.info{
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section input#selectCompanionSceneLink{
|
|
||||||
background-color: #a1a3a3;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section img{
|
|
||||||
width: 160px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section button.selectCharacterButton{
|
|
||||||
position: absolute;
|
|
||||||
top: 20vh;
|
|
||||||
margin: 0;
|
|
||||||
width: 25px;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
|
||||||
left: 1vw;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonRight{
|
|
||||||
right: 1vw;
|
|
||||||
}
|
|
||||||
#selectCompanionScene section button#selectCompanionSceneFormCustomYourOwnSubmit{
|
|
||||||
font-size: 14px;
|
|
||||||
width: auto;
|
|
||||||
margin-top: -2px;
|
|
||||||
background-color: #ffd700;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 800px),
|
|
||||||
only screen and (max-height: 600px) {
|
|
||||||
#selectCompanionScene{
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="selectCompanionScene" hidden>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Select your WOKA</h5>
|
|
||||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
|
||||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<a herf="#" id="selectCompanionSceneFormBack">No companion</a>
|
|
||||||
<button type="submit" id="selectCompanionSceneFormSubmit">Continue</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
3
front/dist/resources/html/gameMenu.html
vendored
@ -1,7 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#gameMenu main{
|
#gameMenu main{
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
3
front/dist/resources/html/gameMenuIcon.html
vendored
@ -1,7 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#menuIcon button {
|
#menuIcon button {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#gameQuality {
|
#gameQuality {
|
||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
border: 1px solid #42464b;
|
border: 1px solid #42464b;
|
||||||
|
3
front/dist/resources/html/gameReport.html
vendored
@ -1,7 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#gameReport {
|
#gameReport {
|
||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
border: 1px solid #42464b;
|
border: 1px solid #42464b;
|
||||||
|
3
front/dist/resources/html/gameShare.html
vendored
@ -1,7 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#gameShare {
|
#gameShare {
|
||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
border: 1px solid #42464b;
|
border: 1px solid #42464b;
|
||||||
|
109
front/dist/resources/html/helpCameraSettings.html
vendored
@ -1,109 +0,0 @@
|
|||||||
<style>
|
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#helpCameraSettings {
|
|
||||||
background: #eceeee;
|
|
||||||
border: 1px solid #42464b;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 25px auto 0;
|
|
||||||
width: 400px;
|
|
||||||
max-height: calc(48vh - 50px);
|
|
||||||
max-width: 48vw;
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
#helpCameraSettings h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section.action{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#helpCameraSettings button {
|
|
||||||
margin: 10px 4px;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
|
||||||
background-color: #c7c7c700;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section a{
|
|
||||||
font-size: 12px;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section h6,
|
|
||||||
#helpCameraSettings section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section p{
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 0px 20px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section p a{
|
|
||||||
font-size: 8px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section p.err{
|
|
||||||
color: #ff0000;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section ul{
|
|
||||||
margin: 6px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section li{
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8px;
|
|
||||||
}
|
|
||||||
#helpCameraSettings section img {
|
|
||||||
width: 200px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 800px),
|
|
||||||
only screen and (max-height: 600px) {
|
|
||||||
#helpCameraSettings{
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="helpCameraSettings" hidden>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Camera/Microphone access needed</h5>
|
|
||||||
<p class="err" id="permissionError">Permission denied</p>
|
|
||||||
<p class="info">You must allow camera and microphone access in your browser.</p>
|
|
||||||
<ul>
|
|
||||||
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
|
||||||
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
|
||||||
</ul>
|
|
||||||
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
|
||||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
|
||||||
<p id='browserHelpSetting'></p>
|
|
||||||
</section>
|
|
||||||
<!--<section class="text-center">
|
|
||||||
<p>If your problem persist, please contact us: <a id="mailto" href="mailto:workadventure@thecodingmachine.com?subject=Support camera and microphone settings" target="_blank"> workadventure@thecodingmachine.com</a>.</p>
|
|
||||||
</section>-->
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<a href="#" id="helpCameraSettingsFormRefresh">Refresh</a>
|
|
||||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
120
front/dist/resources/html/loginScene.html
vendored
@ -1,120 +0,0 @@
|
|||||||
<style>
|
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#loginScene {
|
|
||||||
background: #000000;
|
|
||||||
/*border: 1px solid #ebeeee;*/
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 20px auto 0;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 200px;
|
|
||||||
color: #ebeeee;
|
|
||||||
max-height: 40vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#loginScene h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#loginScene input {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#loginScene section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#loginScene section.action{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#loginScene button {
|
|
||||||
margin: 10px;
|
|
||||||
background-color: black;;
|
|
||||||
color: #ebeeee;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
#loginScene button#loginSceneFormCancel {
|
|
||||||
background-color: #c7c7c700;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#loginScene section h6,
|
|
||||||
#loginScene section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#loginScene section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#loginScene section a{
|
|
||||||
font-size: 8px;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #ebeeee;
|
|
||||||
}
|
|
||||||
#loginScene section a:hover{
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
#loginScene section p{
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 10px 10px;
|
|
||||||
}
|
|
||||||
#loginScene section p.err{
|
|
||||||
color: red;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#loginScene section p.info{
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#loginScene section input#loginSceneLink{
|
|
||||||
background-color: #a1a3a3;
|
|
||||||
}
|
|
||||||
#loginScene section img{
|
|
||||||
width: 160px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 800px),
|
|
||||||
only screen and (max-height: 600px) {
|
|
||||||
#loginScene{
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="loginScene" hidden>
|
|
||||||
<section class="text-center">
|
|
||||||
<img src="resources/logos/logo.png">
|
|
||||||
</section>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Enter your name</h5>
|
|
||||||
<p class="info">9 chars maximum</p>
|
|
||||||
<p class="err" id="errorLoginScene"></p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<input type="text" name="email" id="loginSceneName">
|
|
||||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<button type="submit" id="loginSceneFormSubmit">Continue</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
142
front/dist/resources/html/selectCharacterScene.html
vendored
@ -1,142 +0,0 @@
|
|||||||
<style>
|
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#selectCharacterScene {
|
|
||||||
background: #0000;
|
|
||||||
/*border: 1px solid #ebeeee;*/
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px auto 0;
|
|
||||||
color: #ebeeee;
|
|
||||||
max-height: 48vh;
|
|
||||||
max-width: 300px;
|
|
||||||
width: 40vw;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#selectCharacterScene h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#selectCharacterScene input {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section.action {
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
margin-top: 28vh;
|
|
||||||
}
|
|
||||||
#selectCharacterScene button {
|
|
||||||
margin: 10px 0px;
|
|
||||||
background-color: black;;
|
|
||||||
color: #ebeeee;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
#selectCharacterScene button#selectCharacterSceneFormCancel {
|
|
||||||
background-color: #aca6a600;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section h6,
|
|
||||||
#selectCharacterScene section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section a{
|
|
||||||
font-size: 8px;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #ebeeee;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section a:hover{
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section p{
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 10px 10px;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section p.err{
|
|
||||||
color: red;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section p.info{
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section input#selectCharacterSceneLink{
|
|
||||||
background-color: #a1a3a3;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section img{
|
|
||||||
width: 160px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section button.selectCharacterButton{
|
|
||||||
position: absolute;
|
|
||||||
top: 20vh;
|
|
||||||
margin: 0;
|
|
||||||
width: 25px;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
|
||||||
display: none;
|
|
||||||
left: 1vw;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
|
||||||
display: none;
|
|
||||||
right: 1vw;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section button#selectCharacterSceneFormCustomYourOwnSubmit{
|
|
||||||
font-size: 14px;
|
|
||||||
width: auto;
|
|
||||||
margin-top: -2px;
|
|
||||||
background-color: #ffd700;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 800px),
|
|
||||||
only screen and (max-height: 600px) {
|
|
||||||
#selectCharacterScene{
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="selectCharacterScene" hidden>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Select your WOKA</h5>
|
|
||||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
|
||||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<button type="submit" id="selectCharacterSceneFormSubmit">Continue</button>
|
|
||||||
<button type="submit" id="selectCharacterSceneFormCustomYourOwnSubmit">Custom your WOKA</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
@ -1,7 +1,4 @@
|
|||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: PixelFont-7,monospace!important;
|
|
||||||
}
|
|
||||||
#warningMain {
|
#warningMain {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
BIN
front/dist/resources/objects/arrow_down.png
vendored
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
front/dist/resources/objects/arrow_up.png
vendored
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 4.9 KiB |
BIN
front/dist/resources/objects/arrow_up_black.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
4
front/dist/resources/style/index.scss
vendored
@ -1,4 +0,0 @@
|
|||||||
@import "cowebsite.scss";
|
|
||||||
@import "cowebsite-mobile.scss";
|
|
||||||
@import "style.css";
|
|
||||||
@import "mobile-style.scss";
|
|
@ -4,45 +4,55 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "SEE LICENSE IN LICENSE.txt",
|
"license": "SEE LICENSE IN LICENSE.txt",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tsconfig/svelte": "^1.0.10",
|
||||||
"@types/google-protobuf": "^3.7.3",
|
"@types/google-protobuf": "^3.7.3",
|
||||||
"@types/jasmine": "^3.5.10",
|
"@types/jasmine": "^3.5.10",
|
||||||
|
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||||
|
"@types/node": "^15.3.0",
|
||||||
"@types/quill": "^1.3.7",
|
"@types/quill": "^1.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@types/webpack-dev-server": "^3.11.4",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||||
"css-loader": "^5.1.3",
|
"@typescript-eslint/parser": "^4.23.0",
|
||||||
"eslint": "^6.8.0",
|
"css-loader": "^5.2.4",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"eslint": "^7.26.0",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^6.2.9",
|
||||||
|
"html-webpack-plugin": "^5.3.1",
|
||||||
"jasmine": "^3.5.0",
|
"jasmine": "^3.5.0",
|
||||||
"mini-css-extract-plugin": "^1.3.9",
|
"mini-css-extract-plugin": "^1.6.0",
|
||||||
"sass": "^1.32.8",
|
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||||
"sass-loader": "10.1.1",
|
"sass": "^1.32.12",
|
||||||
"ts-loader": "^6.2.2",
|
"sass-loader": "^11.1.0",
|
||||||
"ts-node": "^8.10.2",
|
"svelte": "^3.38.2",
|
||||||
"typescript": "^3.8.3",
|
"svelte-loader": "^3.1.1",
|
||||||
"webpack": "^4.42.1",
|
"svelte-preprocess": "^4.7.3",
|
||||||
"webpack-cli": "^3.3.11",
|
"ts-loader": "^9.1.2",
|
||||||
"webpack-dev-server": "^3.10.3",
|
"ts-node": "^9.1.1",
|
||||||
"webpack-merge": "^4.2.2"
|
"tsconfig-paths": "^3.9.0",
|
||||||
|
"typescript": "^4.2.4",
|
||||||
|
"webpack": "^5.37.0",
|
||||||
|
"webpack-cli": "^4.7.0",
|
||||||
|
"webpack-dev-server": "^3.11.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/press-start-2p": "^4.3.0",
|
||||||
"@types/simple-peer": "^9.6.0",
|
"@types/simple-peer": "^9.6.0",
|
||||||
"@types/socket.io-client": "^1.4.32",
|
"@types/socket.io-client": "^1.4.32",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"generic-type-guard": "^3.2.0",
|
"generic-type-guard": "^3.2.0",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"phaser": "3.24.1",
|
"phaser": "^3.54.0",
|
||||||
"phaser3-rex-plugins": "^1.1.42",
|
"phaser3-rex-plugins": "^1.1.42",
|
||||||
"queue-typescript": "^1.0.1",
|
"queue-typescript": "^1.0.1",
|
||||||
"quill": "1.3.6",
|
"quill": "1.3.6",
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "^6.6.3",
|
||||||
"simple-peer": "^9.6.2",
|
"simple-peer": "^9.6.2",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"webpack-require-http": "^0.4.3"
|
"standardized-audio-context": "^25.2.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --open",
|
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||||
"build": "webpack --config webpack.prod.js",
|
"build": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||||
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||||
|
|
||||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
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 {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||||
|
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||||
|
import {soundManager} from "../Phaser/Game/SoundManager";
|
||||||
|
|
||||||
export class GlobalMessageManager {
|
export class GlobalMessageManager {
|
||||||
|
|
||||||
@ -43,45 +45,8 @@ export class GlobalMessageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private playAudioMessage(messageId : string, urlMessage: string){
|
private playAudioMessage(messageId : string, urlMessage: string) {
|
||||||
//delete previous elements
|
soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
|
||||||
const previousDivAudio = document.getElementsByClassName('audio-playing');
|
|
||||||
for(let i = 0; i < previousDivAudio.length; i++){
|
|
||||||
previousDivAudio[i].remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
//create new element
|
|
||||||
const divAudio : HTMLDivElement = document.createElement('div');
|
|
||||||
divAudio.id = `audio-playing-${messageId}`;
|
|
||||||
divAudio.classList.add('audio-playing');
|
|
||||||
const imgAudio : HTMLImageElement = document.createElement('img');
|
|
||||||
imgAudio.src = '/resources/logos/megaphone.svg';
|
|
||||||
const pAudio : HTMLParagraphElement = document.createElement('p');
|
|
||||||
pAudio.textContent = 'Message audio'
|
|
||||||
divAudio.appendChild(imgAudio);
|
|
||||||
divAudio.appendChild(pAudio);
|
|
||||||
|
|
||||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
|
||||||
mainSectionDiv.appendChild(divAudio);
|
|
||||||
|
|
||||||
const messageAudio : HTMLAudioElement = document.createElement('audio');
|
|
||||||
messageAudio.id = this.getHtmlMessageId(messageId);
|
|
||||||
messageAudio.autoplay = true;
|
|
||||||
messageAudio.style.display = 'none';
|
|
||||||
messageAudio.onended = () => {
|
|
||||||
divAudio.classList.remove('active');
|
|
||||||
messageAudio.remove();
|
|
||||||
setTimeout(() => {
|
|
||||||
divAudio.remove();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
messageAudio.onplay = () => {
|
|
||||||
divAudio.classList.add('active');
|
|
||||||
}
|
|
||||||
const messageAudioSource : HTMLSourceElement = document.createElement('source');
|
|
||||||
messageAudioSource.src = `${UPLOADER_URL}${urlMessage}`;
|
|
||||||
messageAudio.appendChild(messageAudioSource);
|
|
||||||
mainSectionDiv.appendChild(messageAudio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private playTextMessage(messageId : string, htmlMessage: string){
|
private playTextMessage(messageId : string, htmlMessage: string){
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {TypeMessageInterface} from "./UserMessageManager";
|
import type {TypeMessageInterface} from "./UserMessageManager";
|
||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
|
|
||||||
let modalTimeOut : NodeJS.Timeout;
|
let modalTimeOut : NodeJS.Timeout;
|
||||||
@ -44,7 +44,13 @@ export class TypeMessageExt implements TypeMessageInterface{
|
|||||||
mainSectionDiv.appendChild(div);
|
mainSectionDiv.appendChild(div);
|
||||||
|
|
||||||
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
|
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
|
||||||
reportMessageAudio.play();
|
// FIXME: this will fail on iOS
|
||||||
|
// We should move the sound playing into the GameScene and listen to the event of a report using a store
|
||||||
|
try {
|
||||||
|
reportMessageAudio.play();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
this.nbSecond = this.maxNbSecond;
|
this.nbSecond = this.maxNbSecond;
|
||||||
setTimeout((c) => {
|
setTimeout((c) => {
|
||||||
@ -86,4 +92,4 @@ export class Banned extends TypeMessageExt {
|
|||||||
showMessage(message: string){
|
showMessage(message: string){
|
||||||
super.showMessage(message, false);
|
super.showMessage(message, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,60 @@
|
|||||||
export interface IframeEvent {
|
|
||||||
type: string;
|
|
||||||
data: unknown;
|
import type { ButtonClickedEvent } from './ButtonClickedEvent';
|
||||||
|
import type { ChatEvent } from './ChatEvent';
|
||||||
|
import type { ClosePopupEvent } from './ClosePopupEvent';
|
||||||
|
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||||
|
import type { GoToPageEvent } from './GoToPageEvent';
|
||||||
|
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||||
|
import type { OpenPopupEvent } from './OpenPopupEvent';
|
||||||
|
import type { OpenTabEvent } from './OpenTabEvent';
|
||||||
|
import type { UserInputChatEvent } from './UserInputChatEvent';
|
||||||
|
import type {LoadSoundEvent} from "./LoadSoundEvent";
|
||||||
|
import type {PlaySoundEvent} from "./PlaySoundEvent";
|
||||||
|
|
||||||
|
|
||||||
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IframeEventMap = {
|
||||||
|
//getState: GameStateEvent,
|
||||||
|
// updateTile: UpdateTileEvent
|
||||||
|
chat: ChatEvent,
|
||||||
|
openPopup: OpenPopupEvent
|
||||||
|
closePopup: ClosePopupEvent
|
||||||
|
openTab: OpenTabEvent
|
||||||
|
goToPage: GoToPageEvent
|
||||||
|
openCoWebSite: OpenCoWebSiteEvent
|
||||||
|
closeCoWebSite: null
|
||||||
|
disablePlayerControls: null
|
||||||
|
restorePlayerControls: null
|
||||||
|
displayBubble: null
|
||||||
|
removeBubble: null
|
||||||
|
loadSound: LoadSoundEvent
|
||||||
|
playSound: PlaySoundEvent
|
||||||
|
stopSound: null
|
||||||
|
}
|
||||||
|
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeEventMap[T];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> => typeof event.type === 'string';
|
||||||
|
|
||||||
|
export interface IframeResponseEventMap {
|
||||||
|
userInputChat: UserInputChatEvent
|
||||||
|
enterEvent: EnterLeaveEvent
|
||||||
|
leaveEvent: EnterLeaveEvent
|
||||||
|
buttonClickedEvent: ButtonClickedEvent
|
||||||
|
// gameState: GameStateEvent
|
||||||
|
}
|
||||||
|
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeResponseEventMap[T];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string';
|
export const isIframeResponseEventWrapper = (event: { type?: string }): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === 'string';
|
||||||
|
11
front/src/Api/Events/LoadSoundEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isLoadSoundEvent =
|
||||||
|
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 LoadSoundEvent = tg.GuardedType<typeof isLoadSoundEvent>;
|
24
front/src/Api/Events/PlaySoundEvent.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
|
||||||
|
const isSoundConfig =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
volume: tg.isOptional(tg.isNumber),
|
||||||
|
loop: tg.isOptional(tg.isBoolean),
|
||||||
|
mute: tg.isOptional(tg.isBoolean),
|
||||||
|
rate: tg.isOptional(tg.isNumber),
|
||||||
|
detune: tg.isOptional(tg.isNumber),
|
||||||
|
seek: tg.isOptional(tg.isNumber),
|
||||||
|
delay: tg.isOptional(tg.isNumber)
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
export const isPlaySoundEvent =
|
||||||
|
new tg.IsInterface().withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
config : tg.isOptional(isSoundConfig),
|
||||||
|
}).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type PlaySoundEvent = tg.GuardedType<typeof isPlaySoundEvent>;
|
11
front/src/Api/Events/StopSoundEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isStopSoundEvent =
|
||||||
|
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 StopSoundEvent = tg.GuardedType<typeof isStopSoundEvent>;
|
@ -1,19 +1,20 @@
|
|||||||
import {Subject} from "rxjs";
|
|
||||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
|
||||||
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
|
|
||||||
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
|
||||||
import * as crypto from "crypto";
|
|
||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
|
||||||
import {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
|
||||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
|
||||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
|
||||||
import {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
|
||||||
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
|
||||||
import {scriptUtils} from "./ScriptUtils";
|
|
||||||
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
|
||||||
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
|
||||||
|
|
||||||
|
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||||
|
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||||
|
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||||
|
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||||
|
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||||
|
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||||
|
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||||
|
import { scriptUtils } from "./ScriptUtils";
|
||||||
|
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||||
|
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||||
|
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
||||||
|
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||||
|
import {isPlaySoundEvent, PlaySoundEvent} from "./Events/PlaySoundEvent";
|
||||||
|
import {isStopSoundEvent, StopSoundEvent} from "./Events/StopSoundEvent";
|
||||||
|
import {isLoadSoundEvent, LoadSoundEvent} from "./Events/LoadSoundEvent";
|
||||||
/**
|
/**
|
||||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
* Also allows to send messages to those iframes.
|
* Also allows to send messages to those iframes.
|
||||||
@ -52,22 +53,31 @@ class IframeListener {
|
|||||||
private readonly _removeBubbleStream: Subject<void> = new Subject();
|
private readonly _removeBubbleStream: Subject<void> = new Subject();
|
||||||
public readonly removeBubbleStream = this._removeBubbleStream.asObservable();
|
public readonly removeBubbleStream = this._removeBubbleStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _playSoundStream: Subject<PlaySoundEvent> = new Subject();
|
||||||
|
public readonly playSoundStream = this._playSoundStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _stopSoundStream: Subject<StopSoundEvent> = new Subject();
|
||||||
|
public readonly stopSoundStream = this._stopSoundStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
||||||
|
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
||||||
|
|
||||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener("message", (message) => {
|
window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
|
||||||
// Do we trust the sender of this message?
|
// Do we trust the sender of this message?
|
||||||
// Let's only accept messages from the iframe that are allowed.
|
// 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).
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
let found = false;
|
let foundSrc: string | null = null;
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
if (iframe.contentWindow === message.source) {
|
if (iframe.contentWindow === message.source) {
|
||||||
found = true;
|
foundSrc = iframe.src;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!foundSrc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,28 +90,43 @@ class IframeListener {
|
|||||||
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
||||||
this._closePopupStream.next(payload.data);
|
this._closePopupStream.next(payload.data);
|
||||||
}
|
}
|
||||||
else if(payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
else if (payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
||||||
scriptUtils.openTab(payload.data.url);
|
scriptUtils.openTab(payload.data.url);
|
||||||
}
|
}
|
||||||
else if(payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||||
scriptUtils.goToPage(payload.data.url);
|
scriptUtils.goToPage(payload.data.url);
|
||||||
}
|
}
|
||||||
else if(payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
||||||
scriptUtils.openCoWebsite(payload.data.url);
|
this._playSoundStream.next(payload.data);
|
||||||
}
|
}
|
||||||
else if(payload.type === 'closeCoWebSite') {
|
else if (payload.type === 'stopSound' && isStopSoundEvent(payload.data)) {
|
||||||
|
this._stopSoundStream.next(payload.data);
|
||||||
|
}
|
||||||
|
else if (payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) {
|
||||||
|
this._loadSoundStream.next(payload.data);
|
||||||
|
}
|
||||||
|
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||||
|
const scriptUrl = [...this.scripts.keys()].find(key => {
|
||||||
|
return this.scripts.get(key)?.contentWindow == message.source
|
||||||
|
})
|
||||||
|
|
||||||
|
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (payload.type === 'closeCoWebSite') {
|
||||||
scriptUtils.closeCoWebSite();
|
scriptUtils.closeCoWebSite();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'disablePlayerControl'){
|
|
||||||
|
else if (payload.type === 'disablePlayerControls') {
|
||||||
this._disablePlayerControlStream.next();
|
this._disablePlayerControlStream.next();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'restorePlayerControl'){
|
else if (payload.type === 'restorePlayerControls') {
|
||||||
this._enablePlayerControlStream.next();
|
this._enablePlayerControlStream.next();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'displayBubble'){
|
else if (payload.type === 'displayBubble') {
|
||||||
this._displayBubbleStream.next();
|
this._displayBubbleStream.next();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'removeBubble'){
|
else if (payload.type === 'removeBubble') {
|
||||||
this._removeBubbleStream.next();
|
this._removeBubbleStream.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,7 +155,7 @@ class IframeListener {
|
|||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.id = this.getIFrameId(scriptUrl);
|
iframe.id = this.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
iframe.src = '/iframe.html?script='+encodeURIComponent(scriptUrl);
|
iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl);
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
// 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-scripts');
|
||||||
@ -154,8 +179,8 @@ class IframeListener {
|
|||||||
'\n' +
|
'\n' +
|
||||||
'<html lang="en">\n' +
|
'<html lang="en">\n' +
|
||||||
'<head>\n' +
|
'<head>\n' +
|
||||||
'<script src="'+window.location.protocol+'//'+window.location.host+'/iframe_api.js" ></script>\n' +
|
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
||||||
'<script src="'+scriptUrl+'" ></script>\n' +
|
'<script src="' + scriptUrl + '" ></script>\n' +
|
||||||
'</head>\n' +
|
'</head>\n' +
|
||||||
'</html>\n';
|
'</html>\n';
|
||||||
|
|
||||||
@ -172,14 +197,14 @@ class IframeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getIFrameId(scriptUrl: string): string {
|
private getIFrameId(scriptUrl: string): string {
|
||||||
return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex");
|
return 'script' + btoa(scriptUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterScript(scriptUrl: string): void {
|
unregisterScript(scriptUrl: string): void {
|
||||||
const iFrameId = this.getIFrameId(scriptUrl);
|
const iFrameId = this.getIFrameId(scriptUrl);
|
||||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||||
if (!iframe) {
|
if (!iframe) {
|
||||||
throw new Error('Unknown iframe for script "'+scriptUrl+'"');
|
throw new Error('Unknown iframe for script "' + scriptUrl + '"');
|
||||||
}
|
}
|
||||||
this.unregisterIframe(iframe);
|
this.unregisterIframe(iframe);
|
||||||
iframe.remove();
|
iframe.remove();
|
||||||
@ -227,7 +252,7 @@ class IframeListener {
|
|||||||
/**
|
/**
|
||||||
* Sends the message... to all allowed iframes.
|
* Sends the message... to all allowed iframes.
|
||||||
*/
|
*/
|
||||||
private postMessage(message: IframeEvent) {
|
private postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
iframe.contentWindow?.postMessage(message, '*');
|
iframe.contentWindow?.postMessage(message, '*');
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ class ScriptUtils {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCoWebsite(url : string){
|
public openCoWebsite(url: string, base: string) {
|
||||||
coWebsiteManager.loadCoWebsite(url,url);
|
coWebsiteManager.loadCoWebsite(url, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeCoWebSite(){
|
public closeCoWebSite(){
|
||||||
|
88
front/src/Components/App.svelte
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||||
|
import {menuIconVisible} from "../Stores/MenuStore";
|
||||||
|
import {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
|
||||||
|
import CameraControls from "./CameraControls.svelte";
|
||||||
|
import MyCamera from "./MyCamera.svelte";
|
||||||
|
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||||
|
import {selectCompanionSceneVisibleStore} from "../Stores/SelectCompanionStore";
|
||||||
|
import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore";
|
||||||
|
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||||
|
import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore";
|
||||||
|
import {errorStore} from "../Stores/ErrorStore";
|
||||||
|
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||||
|
import LoginScene from "./Login/LoginScene.svelte";
|
||||||
|
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
|
||||||
|
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||||
|
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||||
|
import {requestVisitCardsStore} from "../Stores/GameStore";
|
||||||
|
|
||||||
|
import {Game} from "../Phaser/Game/Game";
|
||||||
|
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
|
||||||
|
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||||
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
|
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||||
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
|
|
||||||
|
export let game: Game;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if $loginSceneVisibleStore}
|
||||||
|
<div class="scrollable">
|
||||||
|
<LoginScene game={game}></LoginScene>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $selectCharacterSceneVisibleStore}
|
||||||
|
<div>
|
||||||
|
<SelectCharacterScene game={ game }></SelectCharacterScene>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $customCharacterSceneVisibleStore}
|
||||||
|
<div>
|
||||||
|
<CustomCharacterScene game={ game }></CustomCharacterScene>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $selectCompanionSceneVisibleStore}
|
||||||
|
<div>
|
||||||
|
<SelectCompanionScene game={ game }></SelectCompanionScene>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $enableCameraSceneVisibilityStore}
|
||||||
|
<div class="scrollable">
|
||||||
|
<EnableCameraScene game={game}></EnableCameraScene>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $soundPlayingStore}
|
||||||
|
<div>
|
||||||
|
<AudioPlaying url={$soundPlayingStore} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!--
|
||||||
|
{#if $menuIconVisible}
|
||||||
|
<div>
|
||||||
|
<MenuIcon />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
-->
|
||||||
|
{#if $gameOverlayVisibilityStore}
|
||||||
|
<div>
|
||||||
|
<MyCamera></MyCamera>
|
||||||
|
<CameraControls></CameraControls>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $helpCameraSettingsVisibleStore}
|
||||||
|
<div>
|
||||||
|
<HelpCameraSettingsPopup game={ game }></HelpCameraSettingsPopup>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $requestVisitCardsStore}
|
||||||
|
<VisitCard visitCardUrl={$requestVisitCardsStore}></VisitCard>
|
||||||
|
{/if}
|
||||||
|
{#if $errorStore.length > 0}
|
||||||
|
<div>
|
||||||
|
<ErrorDialog></ErrorDialog>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
61
front/src/Components/CameraControls.svelte
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
|
||||||
|
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||||
|
import monitorImg from "./images/monitor.svg";
|
||||||
|
import monitorCloseImg from "./images/monitor-close.svg";
|
||||||
|
import cinemaImg from "./images/cinema.svg";
|
||||||
|
import cinemaCloseImg from "./images/cinema-close.svg";
|
||||||
|
import microphoneImg from "./images/microphone.svg";
|
||||||
|
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||||
|
|
||||||
|
function screenSharingClick(): void {
|
||||||
|
if ($requestedScreenSharingState === true) {
|
||||||
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
|
} else {
|
||||||
|
requestedScreenSharingState.enableScreenSharing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cameraClick(): void {
|
||||||
|
if ($requestedCameraState === true) {
|
||||||
|
requestedCameraState.disableWebcam();
|
||||||
|
} else {
|
||||||
|
requestedCameraState.enableWebcam();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function microphoneClick(): void {
|
||||||
|
if ($requestedMicrophoneState === true) {
|
||||||
|
requestedMicrophoneState.disableMicrophone();
|
||||||
|
} else {
|
||||||
|
requestedMicrophoneState.enableMicrophone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="btn-cam-action">
|
||||||
|
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||||
|
{#if $requestedScreenSharingState}
|
||||||
|
<img src={monitorImg} alt="Start screen sharing">
|
||||||
|
{:else}
|
||||||
|
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}>
|
||||||
|
{#if $requestedCameraState}
|
||||||
|
<img src={cinemaImg} alt="Turn on webcam">
|
||||||
|
{:else}
|
||||||
|
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
|
||||||
|
{#if $requestedMicrophoneState}
|
||||||
|
<img src={microphoneImg} alt="Turn on microphone">
|
||||||
|
{:else}
|
||||||
|
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,119 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { Game } from "../../Phaser/Game/Game";
|
||||||
|
import { CustomizeSceneName } from "../../Phaser/Login/CustomizeScene";
|
||||||
|
|
||||||
|
export let game: Game;
|
||||||
|
|
||||||
|
const customCharacterScene = game.scene.getScene(CustomizeSceneName);
|
||||||
|
let activeRow = customCharacterScene.activeRow;
|
||||||
|
|
||||||
|
function selectLeft() {
|
||||||
|
customCharacterScene.moveCursorHorizontally(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectRight() {
|
||||||
|
customCharacterScene.moveCursorHorizontally(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectUp() {
|
||||||
|
customCharacterScene.moveCursorVertically(-1);
|
||||||
|
activeRow = customCharacterScene.activeRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectDown() {
|
||||||
|
customCharacterScene.moveCursorVertically(1);
|
||||||
|
activeRow = customCharacterScene.activeRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
function previousScene() {
|
||||||
|
customCharacterScene.backToPreviousScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
customCharacterScene.nextSceneToCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="customCharacterScene">
|
||||||
|
<section class="text-center">
|
||||||
|
<h2>Customize your WOKA</h2>
|
||||||
|
</section>
|
||||||
|
<section class="action action-move">
|
||||||
|
<button class="customCharacterSceneButton customCharacterSceneButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||||
|
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||||
|
</section>
|
||||||
|
<section class="action">
|
||||||
|
{#if activeRow === 0}
|
||||||
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
||||||
|
{/if}
|
||||||
|
{#if activeRow !== 0}
|
||||||
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
||||||
|
{/if}
|
||||||
|
{#if activeRow === 5}
|
||||||
|
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
||||||
|
{/if}
|
||||||
|
{#if activeRow !== 5}
|
||||||
|
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
form.customCharacterScene {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
pointer-events: auto;
|
||||||
|
color: #ebeeee;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
&.action {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 55vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.customCharacterSceneButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 33vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.customCharacterSceneFormBack {
|
||||||
|
color: #292929;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
|
||||||
|
&.customCharacterSceneButtonLeft {
|
||||||
|
left: 33vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.customCharacterSceneButtonRight {
|
||||||
|
right: 33vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
form.customCharacterScene button.customCharacterSceneButtonLeft{
|
||||||
|
left: 5vw;
|
||||||
|
}
|
||||||
|
form.customCharacterScene button.customCharacterSceneButtonRight{
|
||||||
|
right: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
217
front/src/Components/EnableCamera/EnableCameraScene.svelte
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {Game} from "../../Phaser/Game/Game";
|
||||||
|
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||||
|
import {
|
||||||
|
audioConstraintStore,
|
||||||
|
cameraListStore,
|
||||||
|
localStreamStore,
|
||||||
|
microphoneListStore,
|
||||||
|
videoConstraintStore
|
||||||
|
} from "../../Stores/MediaStore";
|
||||||
|
import {onDestroy} from "svelte";
|
||||||
|
import HorizontalSoundMeterWidget from "./HorizontalSoundMeterWidget.svelte";
|
||||||
|
import cinemaCloseImg from "../images/cinema-close.svg";
|
||||||
|
import cinemaImg from "../images/cinema.svg";
|
||||||
|
import microphoneImg from "../images/microphone.svg";
|
||||||
|
|
||||||
|
export let game: Game;
|
||||||
|
let selectedCamera : string|null = null;
|
||||||
|
let selectedMicrophone : string|null = null;
|
||||||
|
|
||||||
|
const enableCameraScene = game.scene.getScene(EnableCameraSceneName) as EnableCameraScene;
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
enableCameraScene.login();
|
||||||
|
}
|
||||||
|
|
||||||
|
function srcObject(node, stream) {
|
||||||
|
node.srcObject = stream;
|
||||||
|
return {
|
||||||
|
update(newStream) {
|
||||||
|
if (node.srcObject != newStream) {
|
||||||
|
node.srcObject = newStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream: MediaStream | null;
|
||||||
|
|
||||||
|
const unsubscribe = localStreamStore.subscribe(value => {
|
||||||
|
if (value.type === 'success') {
|
||||||
|
stream = value.stream;
|
||||||
|
|
||||||
|
if (stream !== null) {
|
||||||
|
const videoTracks = stream.getVideoTracks();
|
||||||
|
if (videoTracks.length > 0) {
|
||||||
|
selectedCamera = videoTracks[0].getSettings().deviceId;
|
||||||
|
}
|
||||||
|
const audioTracks = stream.getAudioTracks();
|
||||||
|
if (audioTracks.length > 0) {
|
||||||
|
selectedMicrophone = audioTracks[0].getSettings().deviceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream = null;
|
||||||
|
selectedCamera = null;
|
||||||
|
selectedMicrophone = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(unsubscribe);
|
||||||
|
|
||||||
|
function normalizeDeviceName(label: string): string {
|
||||||
|
// remove text in parenthesis
|
||||||
|
return label.replace(/\([^()]*\)/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCamera() {
|
||||||
|
videoConstraintStore.setDeviceId(selectedCamera);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMicrophone() {
|
||||||
|
audioConstraintStore.setDeviceId(selectedMicrophone);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="enableCameraScene" on:submit|preventDefault={submit}>
|
||||||
|
<section class="text-center">
|
||||||
|
<h2>Turn on your camera and microphone</h2>
|
||||||
|
</section>
|
||||||
|
{#if $localStreamStore.stream}
|
||||||
|
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||||
|
{:else }
|
||||||
|
<div class="webrtcsetup">
|
||||||
|
<img class="background-img" src={cinemaCloseImg} alt="">
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<HorizontalSoundMeterWidget stream={stream}></HorizontalSoundMeterWidget>
|
||||||
|
|
||||||
|
<section class="selectWebcamForm">
|
||||||
|
|
||||||
|
{#if $cameraListStore.length > 1 }
|
||||||
|
<div class="control-group">
|
||||||
|
<img src={cinemaImg} alt="Camera" />
|
||||||
|
<div class="nes-select">
|
||||||
|
<select bind:value={selectedCamera} on:change={selectCamera}>
|
||||||
|
{#each $cameraListStore as camera}
|
||||||
|
<option value={camera.deviceId}>
|
||||||
|
{normalizeDeviceName(camera.label)}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $microphoneListStore.length > 1 }
|
||||||
|
<div class="control-group">
|
||||||
|
<img src={microphoneImg} alt="Microphone" />
|
||||||
|
<div class="nes-select">
|
||||||
|
<select bind:value={selectedMicrophone} on:change={selectMicrophone}>
|
||||||
|
{#each $microphoneListStore as microphone}
|
||||||
|
<option value={microphone.deviceId}>
|
||||||
|
{normalizeDeviceName(microphone.label)}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<section class="action">
|
||||||
|
<button type="submit" class="nes-btn is-primary letsgo" >Let's go!</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.enableCameraScene {
|
||||||
|
pointer-events: auto;
|
||||||
|
margin: 20px auto 0;
|
||||||
|
color: #ebeeee;
|
||||||
|
|
||||||
|
section.selectWebcamForm {
|
||||||
|
margin-top: 3vh;
|
||||||
|
margin-bottom: 3vh;
|
||||||
|
min-height: 10vh;
|
||||||
|
width: 50%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin-top: 1vh;
|
||||||
|
margin-bottom: 1vh;
|
||||||
|
|
||||||
|
option {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.action{
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2{
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.text-center{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.letsgo {
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webrtcsetup{
|
||||||
|
margin-top: 2vh;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
height: 28.125vw;
|
||||||
|
width: 50vw;
|
||||||
|
border: white 6px solid;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img.background-img {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.myCamVideoSetup {
|
||||||
|
margin-top: 2vh;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-height: 50vh;
|
||||||
|
width: 50vw;
|
||||||
|
border: white 6px solid;
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,82 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { AudioContext } from 'standardized-audio-context';
|
||||||
|
import {SoundMeter} from "../../Phaser/Components/SoundMeter";
|
||||||
|
import {onDestroy} from "svelte";
|
||||||
|
|
||||||
|
export let stream: MediaStream | null;
|
||||||
|
let volume = 0;
|
||||||
|
|
||||||
|
const NB_BARS = 20;
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
const soundMeter = new SoundMeter();
|
||||||
|
let display = false;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (stream && stream.getAudioTracks().length > 0) {
|
||||||
|
display = true;
|
||||||
|
soundMeter.connectToSource(stream, new AudioContext());
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
clearInterval(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
try{
|
||||||
|
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||||
|
//console.log(volume);
|
||||||
|
}catch(err){
|
||||||
|
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
display = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
soundMeter.stop();
|
||||||
|
if (timeout) {
|
||||||
|
clearInterval(timeout);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function color(i: number, volume: number) {
|
||||||
|
const red = 255 * i / NB_BARS;
|
||||||
|
const green = 255 * (1 - i / NB_BARS);
|
||||||
|
|
||||||
|
let alpha = 1;
|
||||||
|
if (i >= volume) {
|
||||||
|
alpha = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'background-color:rgba('+red+', '+green+', 0, '+alpha+')';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="horizontal-sound-meter" class:active={display}>
|
||||||
|
{#each [...Array(NB_BARS).keys()] as i}
|
||||||
|
<div style={color(i, volume)}></div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.horizontal-sound-meter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 50%;
|
||||||
|
height: 30px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-sound-meter div {
|
||||||
|
margin-left: 5px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,73 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
|
||||||
|
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||||
|
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
||||||
|
|
||||||
|
let isAndroid = window.navigator.userAgent.includes('Android');
|
||||||
|
let isFirefox = window.navigator.userAgent.includes('Firefox');
|
||||||
|
let isChrome = window.navigator.userAgent.includes('Chrome');
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
helpCameraSettingsVisibleStore.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="helpCameraSettings nes-container" on:submit|preventDefault={close} transition:fly="{{ y: -900, duration: 500 }}">
|
||||||
|
<section>
|
||||||
|
<h2>Camera / Microphone access needed</h2>
|
||||||
|
<p class="err">Permission denied</p>
|
||||||
|
<p>You must allow camera and microphone access in your browser.</p>
|
||||||
|
<p>
|
||||||
|
{#if isFirefox }
|
||||||
|
<p class="err">Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.</p>
|
||||||
|
<img src={firefoxImg} alt="" />
|
||||||
|
{:else if isChrome && !isAndroid }
|
||||||
|
<img src={chromeImg} alt="" />
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}>Refresh</button>
|
||||||
|
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}>Continue without webcam</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.helpCameraSettings {
|
||||||
|
pointer-events: auto;
|
||||||
|
background: #eceeee;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 10vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
max-width: 80vw;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
p {
|
||||||
|
margin: 15px;
|
||||||
|
font-family: 'Press Start 2P';
|
||||||
|
|
||||||
|
& .err {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
123
front/src/Components/Login/LoginScene.svelte
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {Game} from "../../Phaser/Game/Game";
|
||||||
|
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||||
|
import {DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH} from "../../Enum/EnvironmentVariable";
|
||||||
|
import logoImg from "../images/logo.png";
|
||||||
|
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||||
|
import {maxUserNameLength} from "../../Connexion/LocalUser";
|
||||||
|
|
||||||
|
export let game: Game;
|
||||||
|
|
||||||
|
const loginScene = game.scene.getScene(LoginSceneName);
|
||||||
|
|
||||||
|
let name = gameManager.getPlayerName() || '';
|
||||||
|
let startValidating = false;
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
startValidating = true;
|
||||||
|
|
||||||
|
let finalName = name.trim();
|
||||||
|
if (finalName !== '') {
|
||||||
|
loginScene.login(finalName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="loginScene" on:submit|preventDefault={submit}>
|
||||||
|
<section class="text-center">
|
||||||
|
<img src={logoImg} alt="WorkAdventure logo" />
|
||||||
|
</section>
|
||||||
|
<section class="text-center">
|
||||||
|
<h2>Enter your name</h2>
|
||||||
|
</section>
|
||||||
|
<input type="text" name="loginSceneName" class="nes-input is-dark" autofocus maxlength={MAX_USERNAME_LENGTH} bind:value={name} on:keypress={() => {startValidating = true}} class:is-error={name.trim() === '' && startValidating} />
|
||||||
|
<section class="error-section">
|
||||||
|
{#if name.trim() === '' && startValidating }
|
||||||
|
<p class="err">The name is empty</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{#if DISPLAY_TERMS_OF_USE}
|
||||||
|
<section class="terms-and-conditions">
|
||||||
|
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
<section class="action">
|
||||||
|
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.loginScene {
|
||||||
|
pointer-events: auto;
|
||||||
|
margin: 20px auto 0;
|
||||||
|
width: 90%;
|
||||||
|
color: #ebeeee;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column wrap;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-align: center;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terms-and-conditions {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.err {
|
||||||
|
color: #ce372b;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
&.error-section {
|
||||||
|
min-height: 2rem;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.action {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #ebeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: left;
|
||||||
|
margin: 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
33
front/src/Components/Menu/MenuIcon.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="menuIcon">
|
||||||
|
<section>
|
||||||
|
<button>
|
||||||
|
<img src="/static/images/menu.svg" alt="Open menu">
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.menuIcon button {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
img {
|
||||||
|
width: 14px;
|
||||||
|
padding-top: 0;
|
||||||
|
/*cursor: url('/resources/logos/cursor_pointer.png'), pointer;*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menuIcon section {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
@media only screen and (max-height: 700px) {
|
||||||
|
.menuIcon section {
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
46
front/src/Components/MyCamera.svelte
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {localStreamStore} from "../Stores/MediaStore";
|
||||||
|
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||||
|
import {onDestroy} from "svelte";
|
||||||
|
|
||||||
|
function srcObject(node, stream) {
|
||||||
|
node.srcObject = stream;
|
||||||
|
return {
|
||||||
|
update(newStream) {
|
||||||
|
if (node.srcObject != newStream) {
|
||||||
|
node.srcObject = newStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream : MediaStream|null;
|
||||||
|
/*$: {
|
||||||
|
if ($localStreamStore.type === 'success') {
|
||||||
|
stream = $localStreamStore.stream;
|
||||||
|
} else {
|
||||||
|
stream = null;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
const unsubscribe = localStreamStore.subscribe(value => {
|
||||||
|
if (value.type === 'success') {
|
||||||
|
stream = value.stream;
|
||||||
|
} else {
|
||||||
|
stream = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(unsubscribe);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
|
||||||
|
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||||
|
<!-- {#if stream}
|
||||||
|
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||||
|
{/if} -->
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,87 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {Game} from "../../Phaser/Game/Game";
|
||||||
|
import {SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||||
|
|
||||||
|
export let game: Game;
|
||||||
|
|
||||||
|
const selectCompanionScene = game.scene.getScene(SelectCompanionSceneName);
|
||||||
|
|
||||||
|
function selectLeft() {
|
||||||
|
selectCompanionScene.moveToLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectRight() {
|
||||||
|
selectCompanionScene.moveToRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function noCompanion() {
|
||||||
|
selectCompanionScene.closeScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCompanion() {
|
||||||
|
selectCompanionScene.selectCompanion();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="selectCompanionScene">
|
||||||
|
<section class="text-center">
|
||||||
|
<h2>Select your companion</h2>
|
||||||
|
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}> < </button>
|
||||||
|
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={selectRight}> > </button>
|
||||||
|
</section>
|
||||||
|
<section class="action">
|
||||||
|
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}>No companion</button>
|
||||||
|
<button type="submit" class="selectCompanionSceneFormSubmit nes-btn is-primary" on:click|preventDefault={selectCompanion}>Continue</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
form.selectCompanionScene {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
pointer-events: auto;
|
||||||
|
color: #ebeeee;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
&.action {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 55vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.selectCharacterButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 33vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.selectCharacterButtonLeft {
|
||||||
|
left: 33vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.selectCharacterButtonRight {
|
||||||
|
right: 33vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
form.selectCompanionScene button.selectCharacterButtonLeft{
|
||||||
|
left: 5vw;
|
||||||
|
}
|
||||||
|
form.selectCompanionScene button.selectCharacterButtonRight{
|
||||||
|
right: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
53
front/src/Components/SoundMeterWidget.svelte
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { AudioContext } from 'standardized-audio-context';
|
||||||
|
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
||||||
|
import {onDestroy} from "svelte";
|
||||||
|
|
||||||
|
export let stream: MediaStream|null;
|
||||||
|
let volume = 0;
|
||||||
|
|
||||||
|
const NB_BARS = 5;
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
const soundMeter = new SoundMeter();
|
||||||
|
let display = false;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (stream && stream.getAudioTracks().length > 0) {
|
||||||
|
display = true;
|
||||||
|
soundMeter.connectToSource(stream, new AudioContext());
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
clearInterval(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
try{
|
||||||
|
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||||
|
//console.log(volume);
|
||||||
|
}catch(err){
|
||||||
|
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
display = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
soundMeter.stop();
|
||||||
|
if (timeout) {
|
||||||
|
clearInterval(timeout);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="sound-progress" class:active={display}>
|
||||||
|
<span class:active={volume > 1}></span>
|
||||||
|
<span class:active={volume > 2}></span>
|
||||||
|
<span class:active={volume > 3}></span>
|
||||||
|
<span class:active={volume > 4}></span>
|
||||||
|
<span class:active={volume > 5}></span>
|
||||||
|
</div>
|
52
front/src/Components/UI/AudioPlaying.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import megaphoneImg from "./images/megaphone.svg";
|
||||||
|
import {soundPlayingStore} from "../../Stores/SoundPlayingStore";
|
||||||
|
import {afterUpdate, onMount} from "svelte";
|
||||||
|
|
||||||
|
export let url: string;
|
||||||
|
let audio: HTMLAudioElement;
|
||||||
|
|
||||||
|
function soundEnded() {
|
||||||
|
soundPlayingStore.soundEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
audio.play();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="audio-playing" transition:fly="{{ x: 210, duration: 500 }}">
|
||||||
|
<img src={megaphoneImg} alt="Audio playing" />
|
||||||
|
<p>Audio message</p>
|
||||||
|
<audio bind:this={audio} src={url} on:ended={soundEnded} >
|
||||||
|
<track kind="captions">
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/*audio html when audio message playing*/
|
||||||
|
.audio-playing {
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
height: 54px;
|
||||||
|
right: 0;
|
||||||
|
top: 40px;
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 30px 0 0 30px;
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ffda01;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
48
front/src/Components/UI/ErrorDialog.svelte
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {errorStore} from "../../Stores/ErrorStore";
|
||||||
|
|
||||||
|
function close(): boolean {
|
||||||
|
errorStore.clearMessages();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="error-div nes-container is-dark is-rounded" open>
|
||||||
|
<p class="nes-text is-error title">Error</p>
|
||||||
|
<div class="body">
|
||||||
|
{#each $errorStore as error}
|
||||||
|
<p>{error}</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="button-bar">
|
||||||
|
<button class="nes-btn is-error" on:click={close}>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.error-div {
|
||||||
|
pointer-events: auto;
|
||||||
|
margin-top: 10vh;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 80vw;
|
||||||
|
|
||||||
|
.button-bar {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
|
||||||
|
&.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
18
front/src/Components/UI/images/megaphone.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 451.7 512" style="enable-background:new 0 0 451.7 512;" xml:space="preserve">
|
||||||
|
<path d="M436.9,212.6L237.2,12.9c-11.7-11.7-30.7-11.7-42.4,0s-11.7,30.7,0,42.4L394.5,255c11.5,11.9,30.5,12.2,42.4,0.7
|
||||||
|
c11.9-11.5,12.2-30.5,0.7-42.4C437.4,213.1,437.2,212.8,436.9,212.6z"/>
|
||||||
|
<path d="M179.5,83.1l-1.5,7.5c-10.4,53-36,103.4-70.6,144.3l109,108.3c40.7-34.9,90.2-61.5,143.1-72.3l7.5-1.5L179.5,83.1z"/>
|
||||||
|
<path d="M87.4,257l-74.2,74.2c-17.6,17.6-17.6,46.1,0,63.6c0,0,0,0,0,0l42.4,42.4c17.6,17.6,46.1,17.6,63.6,0c0,0,0,0,0,0l74.2-74.2
|
||||||
|
L87.4,257z M98,373.7c-6.1,5.6-15.6,5.3-21.2-0.8c-5.4-5.8-5.4-14.7,0-20.5l21.2-21.2c6-5.8,15.5-5.6,21.2,0.4
|
||||||
|
c5.6,5.8,5.6,15,0,20.8L98,373.7z"/>
|
||||||
|
<path d="M256.1,445.3l20.4-20.4c17.6-17.6,17.6-46.1,0-63.6l-15.1-15.2c-8.4,5.7-16.4,11.7-24.2,18.3l18.1,18.1
|
||||||
|
c5.8,5.9,5.8,15.3,0,21.2l-20.7,20.8l-30.5-29.5l-42.4,42.4l68.1,65.9c11.7,11.7,30.7,11.7,42.4,0c11.7-11.7,11.7-30.7,0-42.4l0,0
|
||||||
|
L256.1,445.3z"/>
|
||||||
|
<path d="M316.7,0c-8.3,0-15,6.7-15,15v30c0,8.3,6.7,15,15,15c8.3,0,15-6.7,15-15V15C331.7,6.7,325,0,316.7,0z"/>
|
||||||
|
<path d="M436.7,120h-30c-8.3,0-15,6.7-15,15s6.7,15,15,15h30c8.3,0,15-6.7,15-15S445,120,436.7,120z"/>
|
||||||
|
<path d="M417.3,34.4c-5.9-5.9-15.4-5.9-21.2,0l-30,30c-6,5.8-6.1,15.3-0.4,21.2c5.8,6,15.3,6.1,21.2,0.4c0.1-0.1,0.2-0.2,0.4-0.4
|
||||||
|
l30-30C423.2,49.7,423.2,40.2,417.3,34.4z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
64
front/src/Components/VisitCard/VisitCard.svelte
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
||||||
|
|
||||||
|
export let visitCardUrl: string;
|
||||||
|
|
||||||
|
function closeCard() {
|
||||||
|
requestVisitCardsStore.set(null);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.visitCard {
|
||||||
|
pointer-events: all;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 515px;
|
||||||
|
margin-top: 200px;
|
||||||
|
|
||||||
|
.defaultCard {
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px black solid;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
width: 500px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
width: 515px;
|
||||||
|
height: 270px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}">
|
||||||
|
{#if visitCardUrl === 'INVALID'}
|
||||||
|
<div class="defaultCard">
|
||||||
|
<header>
|
||||||
|
<h2>Sorry</h2>
|
||||||
|
<p style="font-style: italic;">This user doesn't have a contact card.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main style="padding: 5px; background-color: gray">
|
||||||
|
<p>Maybe he is offline, or this feature is deactivated.</p>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<iframe title="visitCardTitle" src={visitCardUrl}></iframe>
|
||||||
|
{/if}
|
||||||
|
<div class="buttonContainer">
|
||||||
|
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
41
front/src/Components/images/cinema-close.svg
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
|
||||||
|
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
|
||||||
|
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
|
||||||
|
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
|
||||||
|
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<rect x="134.8" y="-45.3" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 376.0669 224.8258)" class="st0" width="19.6" height="460.7"/>
|
||||||
|
<path class="st0" d="M90.6,83.3c-0.2-2.2-1.3-8.9-6.7-14.9c-5.4-5.9-11.9-7.6-14.1-8.1C59.7,49.2,49.5,38,39.4,26.8
|
||||||
|
c24.3-9.8,52-4.4,70.2,13.6c19.9,19.7,24.7,50.8,11.5,76.4C110.9,105.6,100.8,94.5,90.6,83.3z"/>
|
||||||
|
<path class="st0" d="M10.1,51.6c9.4,10.2,18.8,20.4,28.2,30.6c-0.2,1.8-1.4,11.7,5.5,20.5c8.2,10.3,20.7,10.2,22.1,10.1
|
||||||
|
c9.2,10.3,18.5,20.6,27.7,30.8c-4.8,2.3-24.6,11.2-48.3,4.1c-6-1.8-20.7-7.3-32.1-22C-0.3,108.1-0.2,89.1,0.1,83.4
|
||||||
|
C0.8,68,6.8,56.8,10.1,51.6z"/>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M243.4,178.2c0.1,24.5,0.2,49,0.2,73.5c-30.7-33.8-61.3-67.7-92-101.5c5.9,3.9,20.9,12.4,41.6,12.4
|
||||||
|
c16,0,28.2-5.2,34.4-8.4c2.5,1.5,7,4.6,10.7,10.3C242,170,243,175.4,243.4,178.2z"/>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M211.2,311C150.8,258.7,90.4,206.5,30,154.2c6.1,3.1,18.2,8.4,34.4,8.4c18.1,0,31.5-6.5,37.5-9.9
|
||||||
|
c44.5,49,89.1,98.1,133.6,147.1c-1.8,2.1-5.3,5.5-10.6,8.1C219.2,310.6,214,311,211.2,311z"/>
|
||||||
|
<path class="st0" d="M46.8,311C36,267.7,25.2,224.3,14.4,181c0.1-3.2,0.7-11.3,6.5-18.8c3.1-4.1,6.7-6.6,9.1-8
|
||||||
|
C90.4,206.5,150.8,258.7,211.2,311C156.4,311,101.6,311,46.8,311z"/>
|
||||||
|
<path class="st0" d="M14.4,278.6L14.4,278.6c0-32.5,0-65.1,0-97.6c10.8,43.3,21.6,86.7,32.4,130c-2.6,0-12.7-0.4-21.5-8.1
|
||||||
|
C14.7,293.5,14.4,280.7,14.4,278.6z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
front/src/Components/images/logo.png
Normal file
After Width: | Height: | Size: 16 KiB |
27
front/src/Components/images/microphone-close.svg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<rect x="257" y="-47.9" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 643.9641 283.6469)" class="st0" width="20.4" height="628.3"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M333.6,250.3c-52.6-43.9-105.1-87.9-157.7-131.8c0-17.9,0-35.8,0-53.6c6.5-38.6,40.3-67,79.3-66.8
|
||||||
|
c38.6,0.2,71.9,28.5,78.4,66.8C333.6,126.7,333.6,188.5,333.6,250.3z"/>
|
||||||
|
<path class="st0" d="M322.6,279.9c-48.9-53.8-97.8-107.6-146.6-161.4l0,0c52.6,43.9,105.1,87.9,157.7,131.8
|
||||||
|
c-0.2,1.6-0.5,3.3-0.9,5C330.5,265.2,326.6,273.5,322.6,279.9z"/>
|
||||||
|
</g>
|
||||||
|
<path class="st0" d="M292.5,308.1c-2.3,1.2-39.5,20.3-76.7-1c-36.4-20.8-39.4-61.2-39.6-64.1c-0.1-21-0.1-42.1-0.2-63.1
|
||||||
|
C214.8,222.6,253.6,265.3,292.5,308.1z"/>
|
||||||
|
</g>
|
||||||
|
<path class="st0" d="M431.6,238.5c-0.9-8.4-8.5-14.4-16.6-13.5c-7.9,0.9-13.9,8.1-13.2,16.3c-0.1,13.3-2.2,34.6-12.6,57.9
|
||||||
|
c-6.3,14.2-14,25.2-20.6,33.1c6.8,7.5,13.6,14.9,20.3,22.4c9.5-10.9,23.4-29.7,32.8-56.3C430.3,273.9,431.8,252.5,431.6,238.5z"/>
|
||||||
|
<line class="st0" x1="354.5" y1="347.2" x2="374.6" y2="369.4"/>
|
||||||
|
<path class="st0" d="M338.5,359.9c6.8,7.4,13.5,14.9,20.3,22.3c-52.6,37.6-121.5,43.7-179.2,15.8c-60.3-29.1-98.9-90.7-99.3-158.2
|
||||||
|
c0-8.2,6.8-15,15-15s15,6.8,15,15c0.1,13.5,2.4,54.4,32.4,91.6c4.2,5.2,45.1,54.1,113.3,54.1C297,385.6,326.7,367.9,338.5,359.9z"/>
|
||||||
|
<rect x="241" y="409.6" class="st0" width="29.9" height="102.3"/>
|
||||||
|
<path class="st0" d="M304.2,511.9h-97.1c-8-0.4-14.3-7.1-14.3-15c0-8.1,6.7-14.9,15-15c31.7,0,63.4,0.1,95.1,0.1
|
||||||
|
c8.9-0.6,16.3,6.5,16.3,14.9C319.2,504.8,312.6,511.7,304.2,511.9z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,92 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { Game } from "../../Phaser/Game/Game";
|
||||||
|
import { SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
||||||
|
|
||||||
|
export let game: Game;
|
||||||
|
|
||||||
|
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName);
|
||||||
|
|
||||||
|
function selectLeft() {
|
||||||
|
selectCharacterScene.moveToLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectRight() {
|
||||||
|
selectCharacterScene.moveToRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cameraScene() {
|
||||||
|
selectCharacterScene.nextSceneToCameraScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
function customizeScene() {
|
||||||
|
selectCharacterScene.nextSceneToCustomizeScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="selectCharacterScene">
|
||||||
|
<section class="text-center">
|
||||||
|
<h2>Select your WOKA</h2>
|
||||||
|
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||||
|
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||||
|
</section>
|
||||||
|
<section class="action">
|
||||||
|
<button type="submit" class="selectCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ cameraScene }>Continue</button>
|
||||||
|
<button type="submit" class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn" on:click|preventDefault={ customizeScene }>Customize your WOKA</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
form.selectCharacterScene {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
pointer-events: auto;
|
||||||
|
color: #ebeeee;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
&.action {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 55vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.selectCharacterButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 33vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
|
||||||
|
&.selectCharacterButtonLeft {
|
||||||
|
left: 33vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selectCharacterButtonRight {
|
||||||
|
right: 33vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
form.selectCharacterScene button.selectCharacterButtonLeft{
|
||||||
|
left: 5vw;
|
||||||
|
}
|
||||||
|
form.selectCharacterScene button.selectCharacterButtonRight{
|
||||||
|
right: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
@ -1,5 +1,5 @@
|
|||||||
import {Subject} from "rxjs";
|
import {Subject} from "rxjs";
|
||||||
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
import type {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
export enum AdminMessageEventTypes {
|
export enum AdminMessageEventTypes {
|
||||||
admin = 'message',
|
admin = 'message',
|
||||||
@ -19,11 +19,11 @@ interface AdminMessageEvent {
|
|||||||
class AdminMessagesService {
|
class AdminMessagesService {
|
||||||
private _messageStream: Subject<AdminMessageEvent> = new Subject();
|
private _messageStream: Subject<AdminMessageEvent> = new Subject();
|
||||||
public messageStream = this._messageStream.asObservable();
|
public messageStream = this._messageStream.asObservable();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.messageStream.subscribe((event) => console.log('message', event))
|
this.messageStream.subscribe((event) => console.log('message', event))
|
||||||
}
|
}
|
||||||
|
|
||||||
onSendusermessage(message: SendUserMessage|BanUserMessage) {
|
onSendusermessage(message: SendUserMessage|BanUserMessage) {
|
||||||
this._messageStream.next({
|
this._messageStream.next({
|
||||||
type: message.getType() as unknown as AdminMessageEventTypes,
|
type: message.getType() as unknown as AdminMessageEventTypes,
|
||||||
@ -32,4 +32,4 @@ class AdminMessagesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adminMessagesService = new AdminMessagesService();
|
export const adminMessagesService = new AdminMessagesService();
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {PUSHER_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 type {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 {CharacterTexture, LocalUser} from "./LocalUser";
|
||||||
import {Room} from "./Room";
|
import {Room} from "./Room";
|
||||||
|
|
||||||
|
|
||||||
@ -46,8 +46,8 @@ class ConnectionManager {
|
|||||||
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) {
|
||||||
const localUser = localUserStore.getLocalUser();
|
|
||||||
|
|
||||||
|
let localUser = localUserStore.getLocalUser();
|
||||||
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
|
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
|
||||||
this.localUser = localUser;
|
this.localUser = localUser;
|
||||||
try {
|
try {
|
||||||
@ -57,16 +57,42 @@ class ConnectionManager {
|
|||||||
console.error('JWT token invalid. Did it expire? Login anonymously instead.');
|
console.error('JWT token invalid. Did it expire? Login anonymously instead.');
|
||||||
await this.anonymousLogin();
|
await this.anonymousLogin();
|
||||||
}
|
}
|
||||||
} else {
|
}else{
|
||||||
await this.anonymousLogin();
|
await this.anonymousLogin();
|
||||||
}
|
}
|
||||||
let roomId: string
|
|
||||||
|
localUser = localUserStore.getLocalUser();
|
||||||
|
if(!localUser){
|
||||||
|
throw "Error to store local user data";
|
||||||
|
}
|
||||||
|
|
||||||
|
let roomId: string;
|
||||||
if (connexionType === GameConnexionTypes.empty) {
|
if (connexionType === GameConnexionTypes.empty) {
|
||||||
roomId = START_ROOM_URL;
|
roomId = START_ROOM_URL;
|
||||||
} else {
|
} else {
|
||||||
roomId = window.location.pathname + window.location.search + window.location.hash;
|
roomId = window.location.pathname + window.location.search + window.location.hash;
|
||||||
}
|
}
|
||||||
return Promise.resolve(new Room(roomId));
|
|
||||||
|
//get detail map for anonymous login and set texture in local storage
|
||||||
|
const room = new Room(roomId);
|
||||||
|
const mapDetail = await room.getMapDetail();
|
||||||
|
if(mapDetail.textures != undefined && mapDetail.textures.length > 0) {
|
||||||
|
//check if texture was changed
|
||||||
|
if(localUser.textures.length === 0){
|
||||||
|
localUser.textures = mapDetail.textures;
|
||||||
|
}else{
|
||||||
|
mapDetail.textures.forEach((newTexture) => {
|
||||||
|
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
||||||
|
if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localUser?.textures.push(newTexture)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.localUser = localUser;
|
||||||
|
localUserStore.saveUser(localUser);
|
||||||
|
}
|
||||||
|
return Promise.resolve(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error('Invalid URL'));
|
return Promise.reject(new Error('Invalid URL'));
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
import {SignalData} from "simple-peer";
|
import type {SignalData} from "simple-peer";
|
||||||
import {RoomConnection} from "./RoomConnection";
|
import type {RoomConnection} from "./RoomConnection";
|
||||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||||
|
|
||||||
export enum EventMessage{
|
export enum EventMessage{
|
||||||
CONNECT = "connect",
|
CONNECT = "connect",
|
||||||
|
19
front/src/Connexion/EmoteEventStream.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
interface EmoteEvent {
|
||||||
|
userId: number,
|
||||||
|
emoteName: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmoteEventStream {
|
||||||
|
|
||||||
|
private _stream:Subject<EmoteEvent> = new Subject();
|
||||||
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
|
fire(userId: number, emoteName:string) {
|
||||||
|
this._stream.next({userId, emoteName});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emoteEventStream = new EmoteEventStream();
|
@ -9,9 +9,8 @@ export interface CharacterTexture {
|
|||||||
|
|
||||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||||
|
|
||||||
export function isUserNameValid(value: string): boolean {
|
export function isUserNameValid(value: unknown): boolean {
|
||||||
const regexp = new RegExp('^[A-Za-z]{1,'+maxUserNameLength+'}$');
|
return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && value.indexOf(' ') === -1;
|
||||||
return regexp.test(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function areCharacterLayersValid(value: string[] | null): boolean {
|
export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||||
@ -25,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LocalUser {
|
export class LocalUser {
|
||||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
|
constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
||||||
|
import type {CharacterTexture} from "./LocalUser";
|
||||||
|
|
||||||
|
export class MapDetail{
|
||||||
|
constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 textures: CharacterTexture[]|undefined;
|
||||||
private instance: string|undefined;
|
private instance: string|undefined;
|
||||||
private _search: URLSearchParams;
|
private _search: URLSearchParams;
|
||||||
|
|
||||||
@ -50,10 +57,10 @@ export class Room {
|
|||||||
return {roomId, hash}
|
return {roomId, hash}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMapUrl(): Promise<string> {
|
public async getMapDetail(): Promise<MapDetail> {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<MapDetail>((resolve, reject) => {
|
||||||
if (this.mapUrl !== undefined) {
|
if (this.mapUrl !== undefined && this.textures != undefined) {
|
||||||
resolve(this.mapUrl);
|
resolve(new MapDetail(this.mapUrl, this.textures));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +68,7 @@ export class Room {
|
|||||||
const match = /_\/[^/]+\/(.+)/.exec(this.id);
|
const match = /_\/[^/]+\/(.+)/.exec(this.id);
|
||||||
if (!match) throw new Error('Could not extract url from "'+this.id+'"');
|
if (!match) throw new Error('Could not extract url from "'+this.id+'"');
|
||||||
this.mapUrl = window.location.protocol+'//'+match[1];
|
this.mapUrl = window.location.protocol+'//'+match[1];
|
||||||
resolve(this.mapUrl);
|
resolve(new MapDetail(this.mapUrl, this.textures));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// 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.
|
||||||
@ -71,7 +78,7 @@ export class Room {
|
|||||||
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);
|
||||||
resolve(data.mapUrl);
|
resolve(data);
|
||||||
return;
|
return;
|
||||||
}).catch((reason) => {
|
}).catch((reason) => {
|
||||||
reject(reason);
|
reject(reason);
|
||||||
|
@ -27,11 +27,13 @@ import {
|
|||||||
SendJitsiJwtMessage,
|
SendJitsiJwtMessage,
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
PingMessage,
|
PingMessage,
|
||||||
SendUserMessage,
|
EmoteEventMessage,
|
||||||
BanUserMessage
|
EmotePromptMessage,
|
||||||
|
SendUserMessage,
|
||||||
|
BanUserMessage, RequestVisitCardMessage
|
||||||
} from "../Messages/generated/messages_pb"
|
} from "../Messages/generated/messages_pb"
|
||||||
|
|
||||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
|
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
|
||||||
import {
|
import {
|
||||||
@ -42,11 +44,13 @@ import {
|
|||||||
ViewportInterface, WebRtcDisconnectMessageInterface,
|
ViewportInterface, WebRtcDisconnectMessageInterface,
|
||||||
WebRtcSignalReceivedMessageInterface,
|
WebRtcSignalReceivedMessageInterface,
|
||||||
} from "./ConnexionModels";
|
} from "./ConnexionModels";
|
||||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||||
import {adminMessagesService} from "./AdminMessagesService";
|
import {adminMessagesService} from "./AdminMessagesService";
|
||||||
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||||
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||||
import {connectionManager} from "./ConnectionManager";
|
import {connectionManager} from "./ConnectionManager";
|
||||||
|
import {emoteEventStream} from "./EmoteEventStream";
|
||||||
|
import {requestVisitCardsStore} from "../Stores/GameStore";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -86,7 +90,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
url += '&bottom='+Math.floor(viewport.bottom);
|
url += '&bottom='+Math.floor(viewport.bottom);
|
||||||
url += '&left='+Math.floor(viewport.left);
|
url += '&left='+Math.floor(viewport.left);
|
||||||
url += '&right='+Math.floor(viewport.right);
|
url += '&right='+Math.floor(viewport.right);
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
if (typeof companion === 'string') {
|
||||||
url += '&companion='+encodeURIComponent(companion);
|
url += '&companion='+encodeURIComponent(companion);
|
||||||
}
|
}
|
||||||
@ -124,7 +128,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
|
|
||||||
if (message.hasBatchmessage()) {
|
if (message.hasBatchmessage()) {
|
||||||
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
||||||
let event: string;
|
let event: string|null = null;
|
||||||
let payload;
|
let payload;
|
||||||
if (subMessage.hasUsermovedmessage()) {
|
if (subMessage.hasUsermovedmessage()) {
|
||||||
event = EventMessage.USER_MOVED;
|
event = EventMessage.USER_MOVED;
|
||||||
@ -144,11 +148,16 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (subMessage.hasItemeventmessage()) {
|
} else if (subMessage.hasItemeventmessage()) {
|
||||||
event = EventMessage.ITEM_EVENT;
|
event = EventMessage.ITEM_EVENT;
|
||||||
payload = subMessage.getItemeventmessage();
|
payload = subMessage.getItemeventmessage();
|
||||||
|
} else if (subMessage.hasEmoteeventmessage()) {
|
||||||
|
const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
|
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected batch message type');
|
throw new Error('Unexpected batch message type');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatch(event, payload);
|
if (event) {
|
||||||
|
this.dispatch(event, payload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (message.hasRoomjoinedmessage()) {
|
} else if (message.hasRoomjoinedmessage()) {
|
||||||
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
||||||
@ -195,6 +204,8 @@ export class RoomConnection implements RoomConnection {
|
|||||||
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
|
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
|
||||||
} else if (message.hasWorldfullwarningmessage()) {
|
} else if (message.hasWorldfullwarningmessage()) {
|
||||||
worldFullWarningStream.onMessage();
|
worldFullWarningStream.onMessage();
|
||||||
|
} else if (message.hasVisitcardmessage()) {
|
||||||
|
requestVisitCardsStore.set(message?.getVisitcardmessage()?.getUrl() as unknown as string);
|
||||||
} else if (message.hasRefreshroommessage()) {
|
} else if (message.hasRefreshroommessage()) {
|
||||||
//todo: implement a way to notify the user the room was refreshed.
|
//todo: implement a way to notify the user the room was refreshed.
|
||||||
} else {
|
} else {
|
||||||
@ -599,4 +610,24 @@ export class RoomConnection implements RoomConnection {
|
|||||||
public isAdmin(): boolean {
|
public isAdmin(): boolean {
|
||||||
return this.hasTag('admin');
|
return this.hasTag('admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(emoteName: string): void {
|
||||||
|
const emoteMessage = new EmotePromptMessage();
|
||||||
|
emoteMessage.setEmote(emoteName)
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setEmotepromptmessage(emoteMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public requestVisitCardUrl(targetUserId: number): void {
|
||||||
|
const message = new RequestVisitCardMessage();
|
||||||
|
message.setTargetuserid(targetUserId);
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setRequestvisitcardmessage(message);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,30 @@
|
|||||||
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';
|
||||||
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
|
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
|
||||||
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
|
|
||||||
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.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 SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||||
|
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
||||||
const TURN_USER: string = process.env.TURN_USER || '';
|
const TURN_USER: string = process.env.TURN_USER || '';
|
||||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
||||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||||
const RESOLUTION = 2;
|
|
||||||
const ZOOM_LEVEL = 1/*3/4*/;
|
|
||||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
|
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
|
||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
||||||
|
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
|
||||||
|
|
||||||
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
|
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
START_ROOM_URL,
|
START_ROOM_URL,
|
||||||
|
SKIP_RENDER_OPTIMIZATIONS,
|
||||||
|
DISABLE_NOTIFICATIONS,
|
||||||
PUSHER_URL,
|
PUSHER_URL,
|
||||||
UPLOADER_URL,
|
UPLOADER_URL,
|
||||||
ADMIN_URL,
|
|
||||||
RESOLUTION,
|
|
||||||
ZOOM_LEVEL,
|
|
||||||
POSITION_DELAY,
|
POSITION_DELAY,
|
||||||
MAX_EXTRAPOLATION_TIME,
|
MAX_EXTRAPOLATION_TIME,
|
||||||
STUN_SERVER,
|
STUN_SERVER,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {PositionMessage} from "../Messages/generated/messages_pb";
|
import {PositionMessage} from "../Messages/generated/messages_pb";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
import {PointInterface} from "../Connexion/ConnexionModels";
|
import type {PointInterface} from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
export class ProtobufClientUtils {
|
export class ProtobufClientUtils {
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import Container = Phaser.GameObjects.Container;
|
import Container = Phaser.GameObjects.Container;
|
||||||
import { lazyLoadCompanionResource } from "./CompanionTexturesLoadingManager";
|
|
||||||
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||||
|
|
||||||
export interface CompanionStatus {
|
export interface CompanionStatus {
|
||||||
@ -25,7 +24,7 @@ export class Companion extends Container {
|
|||||||
|
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
|
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
|
||||||
super(scene, x + 14, y + 4);
|
super(scene, x + 14, y + 4);
|
||||||
|
|
||||||
this.sprites = new Map<string, Sprite>();
|
this.sprites = new Map<string, Sprite>();
|
||||||
|
|
||||||
this.delta = 0;
|
this.delta = 0;
|
||||||
@ -104,7 +103,7 @@ export class Companion extends Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setDepth(this.y);
|
this.setDepth(this.y);
|
||||||
this.playAnimation(this.direction, this.animationType);
|
this.playAnimation(this.direction, this.animationType);
|
||||||
}
|
}
|
||||||
@ -137,7 +136,7 @@ export class Companion extends Container {
|
|||||||
this.getAnimations(resource).forEach(animation => {
|
this.getAnimations(resource).forEach(animation => {
|
||||||
this.scene.anims.create(animation);
|
this.scene.anims.create(animation);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scene.sys.updateList.add(sprite);
|
this.scene.sys.updateList.add(sprite);
|
||||||
this.sprites.set(resource, sprite);
|
this.sprites.set(resource, sprite);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, 'layout_modes', 3);
|
super(scene, x, y, 'layout_modes', 3);
|
||||||
@ -6,6 +8,6 @@ export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
||||||
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
const outOfScreenX = -1000;
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
const outOfScreenY = -1000;
|
|
||||||
|
|
||||||
|
|
||||||
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
||||||
export const joystickBaseKey = 'joystickBase';
|
export const joystickBaseKey = 'joystickBase';
|
||||||
@ -10,26 +8,58 @@ export const joystickBaseImg = 'resources/objects/joystickSplitted.png';
|
|||||||
export const joystickThumbKey = 'joystickThumb';
|
export const joystickThumbKey = 'joystickThumb';
|
||||||
export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png';
|
export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png';
|
||||||
|
|
||||||
|
const baseSize = 50;
|
||||||
|
const thumbSize = 25;
|
||||||
|
const radius = 17.5;
|
||||||
|
|
||||||
export class MobileJoystick extends VirtualJoystick {
|
export class MobileJoystick extends VirtualJoystick {
|
||||||
|
private resizeCallback: () => void;
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene) {
|
constructor(scene: Phaser.Scene) {
|
||||||
super(scene, {
|
super(scene, {
|
||||||
x: outOfScreenX,
|
x: -1000,
|
||||||
y: outOfScreenY,
|
y: -1000,
|
||||||
radius: 20,
|
radius: radius * window.devicePixelRatio,
|
||||||
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(60, 60).setDepth(99999),
|
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
|
||||||
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(30, 30).setDepth(99999),
|
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
|
||||||
enable: true,
|
enable: true,
|
||||||
dir: "8dir",
|
dir: "8dir",
|
||||||
});
|
});
|
||||||
|
this.visible = false;
|
||||||
|
this.enable = false;
|
||||||
|
|
||||||
this.scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => {
|
this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||||
this.x = pointer.x;
|
if (!pointer.wasTouch) {
|
||||||
this.y = pointer.y;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's only display the joystick if there is one finger on the screen
|
||||||
|
if ((pointer.event as TouchEvent).touches.length === 1) {
|
||||||
|
this.x = pointer.x;
|
||||||
|
this.y = pointer.y;
|
||||||
|
this.visible = true;
|
||||||
|
this.enable = true;
|
||||||
|
} else {
|
||||||
|
this.visible = false;
|
||||||
|
this.enable = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.scene.input.on('pointerup', () => {
|
this.scene.input.on('pointerup', () => {
|
||||||
this.x = outOfScreenX;
|
this.visible = false;
|
||||||
this.y = outOfScreenY;
|
this.enable = false;
|
||||||
});
|
});
|
||||||
|
this.resizeCallback = this.resize.bind(this);
|
||||||
|
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private resize() {
|
||||||
|
this.base.setDisplaySize(baseSize / waScaleManager.zoomModifier * window.devicePixelRatio, baseSize / waScaleManager.zoomModifier * window.devicePixelRatio);
|
||||||
|
this.thumb.setDisplaySize(thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio, thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio);
|
||||||
|
this.setRadius(radius / waScaleManager.zoomModifier * window.devicePixelRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
super.destroy();
|
||||||
|
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export const openChatIconName = 'openChatIcon';
|
export const openChatIconName = 'openChatIcon';
|
||||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||||
@ -9,7 +10,7 @@ export class OpenChatIcon extends Phaser.GameObjects.Image {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
|
|
||||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, 'layout_modes', 0);
|
super(scene, x, y, 'layout_modes', 0);
|
||||||
@ -6,6 +8,6 @@ export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
74
front/src/Phaser/Components/RadialMenu.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
|
import {DEPTH_UI_INDEX} from "../Game/DepthIndexes";
|
||||||
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
|
||||||
|
export interface RadialMenuItem {
|
||||||
|
image: string,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RadialMenuClickEvent = 'radialClick';
|
||||||
|
|
||||||
|
export class RadialMenu extends Phaser.GameObjects.Container {
|
||||||
|
private resizeCallback: OmitThisParameter<() => void>;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number, private items: RadialMenuItem[]) {
|
||||||
|
super(scene, x, y);
|
||||||
|
this.setDepth(DEPTH_UI_INDEX)
|
||||||
|
this.scene.add.existing(this);
|
||||||
|
this.initItems();
|
||||||
|
|
||||||
|
this.resize();
|
||||||
|
this.resizeCallback = this.resize.bind(this);
|
||||||
|
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initItems() {
|
||||||
|
const itemsNumber = this.items.length;
|
||||||
|
const menuRadius = 70 + (waScaleManager.uiScalingFactor - 1) * 20;
|
||||||
|
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius))
|
||||||
|
}
|
||||||
|
|
||||||
|
private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number, menuRadius: number) {
|
||||||
|
const image = new Sprite(this.scene, 0, menuRadius, item.image);
|
||||||
|
this.add(image);
|
||||||
|
this.scene.sys.updateList.add(image);
|
||||||
|
const scalingFactor = waScaleManager.uiScalingFactor * 0.075;
|
||||||
|
image.setScale(scalingFactor)
|
||||||
|
image.setInteractive({
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
|
||||||
|
image.on('pointerover', () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: image,
|
||||||
|
props: {
|
||||||
|
scale: 2 * scalingFactor,
|
||||||
|
},
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power3',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
image.on('pointerout', () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: image,
|
||||||
|
props: {
|
||||||
|
scale: scalingFactor,
|
||||||
|
},
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power3',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const angle = 2 * Math.PI * index / itemsNumber;
|
||||||
|
Phaser.Actions.RotateAroundDistance([image], {x: 0, y: 0}, angle, menuRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resize() {
|
||||||
|
this.setScale(waScaleManager.uiScalingFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to measure the sound volume of a media stream
|
* Class to measure the sound volume of a media stream
|
||||||
*/
|
*/
|
||||||
@ -5,10 +7,10 @@ export class SoundMeter {
|
|||||||
private instant: number;
|
private instant: number;
|
||||||
private clip: number;
|
private clip: number;
|
||||||
//private script: ScriptProcessorNode;
|
//private script: ScriptProcessorNode;
|
||||||
private analyser: AnalyserNode|undefined;
|
private analyser: IAnalyserNode<IAudioContext>|undefined;
|
||||||
private dataArray: Uint8Array|undefined;
|
private dataArray: Uint8Array|undefined;
|
||||||
private context: AudioContext|undefined;
|
private context: IAudioContext|undefined;
|
||||||
private source: MediaStreamAudioSourceNode|undefined;
|
private source: IMediaStreamAudioSourceNode<IAudioContext>|undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.instant = 0.0;
|
this.instant = 0.0;
|
||||||
@ -16,7 +18,7 @@ export class SoundMeter {
|
|||||||
//this.script = context.createScriptProcessor(2048, 1, 1);
|
//this.script = context.createScriptProcessor(2048, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private init(context: AudioContext) {
|
private init(context: IAudioContext) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.analyser = this.context.createAnalyser();
|
this.analyser = this.context.createAnalyser();
|
||||||
|
|
||||||
@ -25,8 +27,12 @@ export class SoundMeter {
|
|||||||
this.dataArray = new Uint8Array(bufferLength);
|
this.dataArray = new Uint8Array(bufferLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectToSource(stream: MediaStream, context: AudioContext): void
|
public connectToSource(stream: MediaStream, context: IAudioContext): void
|
||||||
{
|
{
|
||||||
|
if (this.source !== undefined) {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
this.init(context);
|
this.init(context);
|
||||||
|
|
||||||
this.source = this.context?.createMediaStreamSource(stream);
|
this.source = this.context?.createMediaStreamSource(stream);
|
||||||
@ -81,56 +87,3 @@ export class SoundMeter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Meter class that generates a number correlated to audio volume.
|
|
||||||
// The meter class itself displays nothing, but it makes the
|
|
||||||
// instantaneous and time-decaying volumes available for inspection.
|
|
||||||
// It also reports on the fraction of samples that were at or near
|
|
||||||
// the top of the measurement range.
|
|
||||||
/*function SoundMeter(context) {
|
|
||||||
this.context = context;
|
|
||||||
this.instant = 0.0;
|
|
||||||
this.slow = 0.0;
|
|
||||||
this.clip = 0.0;
|
|
||||||
this.script = context.createScriptProcessor(2048, 1, 1);
|
|
||||||
const that = this;
|
|
||||||
this.script.onaudioprocess = function(event) {
|
|
||||||
const input = event.inputBuffer.getChannelData(0);
|
|
||||||
let i;
|
|
||||||
let sum = 0.0;
|
|
||||||
let clipcount = 0;
|
|
||||||
for (i = 0; i < input.length; ++i) {
|
|
||||||
sum += input[i] * input[i];
|
|
||||||
if (Math.abs(input[i]) > 0.99) {
|
|
||||||
clipcount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
that.instant = Math.sqrt(sum / input.length);
|
|
||||||
that.slow = 0.95 * that.slow + 0.05 * that.instant;
|
|
||||||
that.clip = clipcount / input.length;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
SoundMeter.prototype.connectToSource = function(stream, callback) {
|
|
||||||
console.log('SoundMeter connecting');
|
|
||||||
try {
|
|
||||||
this.mic = this.context.createMediaStreamSource(stream);
|
|
||||||
this.mic.connect(this.script);
|
|
||||||
// necessary to make sample run, but should not be.
|
|
||||||
this.script.connect(this.context.destination);
|
|
||||||
if (typeof callback !== 'undefined') {
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
if (typeof callback !== 'undefined') {
|
|
||||||
callback(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SoundMeter.prototype.stop = function() {
|
|
||||||
this.mic.disconnect();
|
|
||||||
this.script.disconnect();
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import Container = Phaser.GameObjects.Container;
|
|
||||||
import {Scene} from "phaser";
|
|
||||||
import GameObject = Phaser.GameObjects.GameObject;
|
|
||||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
|
||||||
|
|
||||||
|
|
||||||
export class SoundMeterSprite extends Container {
|
|
||||||
private rectangles: Rectangle[] = new Array<Rectangle>();
|
|
||||||
private static readonly NB_BARS = 20;
|
|
||||||
|
|
||||||
constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) {
|
|
||||||
super(scene, x, y, children);
|
|
||||||
|
|
||||||
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
|
|
||||||
const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16));
|
|
||||||
this.add(rectangle);
|
|
||||||
this.rectangles.push(rectangle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A number between 0 and 100
|
|
||||||
*
|
|
||||||
* @param volume
|
|
||||||
*/
|
|
||||||
public setVolume(volume: number): void {
|
|
||||||
|
|
||||||
const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS;
|
|
||||||
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
|
|
||||||
if (normalizedVolume < i) {
|
|
||||||
this.rectangles[i].alpha = 0.5;
|
|
||||||
} else {
|
|
||||||
this.rectangles[i].alpha = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWidth(): number {
|
|
||||||
return SoundMeterSprite.NB_BARS * 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,5 @@
|
|||||||
import {ITiledMapObject} from "../Map/ITiledMap";
|
import type {ITiledMapObject} from "../Map/ITiledMap";
|
||||||
import Text = Phaser.GameObjects.Text;
|
import type {GameScene} from "../Game/GameScene";
|
||||||
import {GameScene} from "../Game/GameScene";
|
|
||||||
import TextStyle = Phaser.GameObjects.TextStyle;
|
|
||||||
|
|
||||||
export class TextUtils {
|
export class TextUtils {
|
||||||
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
|
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
|
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
|
||||||
import {SpeechBubble} from "./SpeechBubble";
|
import {SpeechBubble} from "./SpeechBubble";
|
||||||
import BitmapText = Phaser.GameObjects.BitmapText;
|
import Text = Phaser.GameObjects.Text;
|
||||||
import Container = Phaser.GameObjects.Container;
|
import Container = Phaser.GameObjects.Container;
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import {TextureError} from "../../Exception/TextureError";
|
import {TextureError} from "../../Exception/TextureError";
|
||||||
import {Companion} from "../Companion/Companion";
|
import {Companion} from "../Companion/Companion";
|
||||||
|
import type {GameScene} from "../Game/GameScene";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
|
||||||
|
const playerNameY = - 25;
|
||||||
|
|
||||||
interface AnimationData {
|
interface AnimationData {
|
||||||
key: string;
|
key: string;
|
||||||
@ -14,24 +19,30 @@ interface AnimationData {
|
|||||||
frames : number[]
|
frames : number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interactiveRadius = 35;
|
||||||
|
|
||||||
export abstract class Character extends Container {
|
export abstract class Character extends Container {
|
||||||
private bubble: SpeechBubble|null = null;
|
private bubble: SpeechBubble|null = null;
|
||||||
private readonly playerName: BitmapText;
|
private readonly playerName: Text;
|
||||||
public PlayerValue: string;
|
public PlayerValue: string;
|
||||||
public sprites: Map<string, Sprite>;
|
public sprites: Map<string, Sprite>;
|
||||||
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||||
//private teleportation: Sprite;
|
//private teleportation: Sprite;
|
||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
|
private emote: Phaser.GameObjects.Sprite | null = null;
|
||||||
|
private emoteTween: Phaser.Tweens.Tween|null = null;
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene,
|
constructor(scene: GameScene,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: Promise<string[]>,
|
||||||
name: string,
|
name: string,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
frame?: string | number
|
frame: string | number,
|
||||||
|
companion: string|null,
|
||||||
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(scene, x, y/*, texture, frame*/);
|
super(scene, x, y/*, texture, frame*/);
|
||||||
this.PlayerValue = name;
|
this.PlayerValue = name;
|
||||||
@ -44,20 +55,19 @@ export abstract class Character extends Container {
|
|||||||
this.addTextures(textures, frame);
|
this.addTextures(textures, frame);
|
||||||
this.invisible = false
|
this.invisible = false
|
||||||
})
|
})
|
||||||
|
|
||||||
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
|
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"});
|
||||||
this.teleportation.setInteractive();
|
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
this.teleportation.visible = false;
|
|
||||||
this.teleportation.on('pointerup', () => {
|
|
||||||
this.report.visible = false;
|
|
||||||
this.teleportation.visible = false;
|
|
||||||
});
|
|
||||||
this.add(this.teleportation);*/
|
|
||||||
|
|
||||||
this.playerName = new BitmapText(scene, 0, - 25, 'main_font', name, 7);
|
|
||||||
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(99999);
|
|
||||||
this.add(this.playerName);
|
this.add(this.playerName);
|
||||||
|
|
||||||
|
if (this.isClickable()) {
|
||||||
|
this.setInteractive({
|
||||||
|
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||||
|
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
|
|
||||||
this.scene.physics.world.enableBody(this);
|
this.scene.physics.world.enableBody(this);
|
||||||
@ -69,6 +79,10 @@ export abstract class Character extends Container {
|
|||||||
this.setDepth(-1);
|
this.setDepth(-1);
|
||||||
|
|
||||||
this.playAnimation(direction, moving);
|
this.playAnimation(direction, moving);
|
||||||
|
|
||||||
|
if (typeof companion === 'string') {
|
||||||
|
this.addCompanion(companion, companionTexturePromise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||||
@ -76,6 +90,8 @@ export abstract class Character extends Container {
|
|||||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract isClickable(): boolean;
|
||||||
|
|
||||||
public addTextures(textures: string[], frame?: string | number): void {
|
public addTextures(textures: string[], frame?: string | number): void {
|
||||||
for (const texture of textures) {
|
for (const texture of textures) {
|
||||||
@ -83,7 +99,6 @@ export abstract class Character extends Container {
|
|||||||
throw new TextureError('texture not found');
|
throw new TextureError('texture not found');
|
||||||
}
|
}
|
||||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||||
sprite.setInteractive({useHandCursor: true});
|
|
||||||
this.add(sprite);
|
this.add(sprite);
|
||||||
this.getPlayerAnimations(texture).forEach(d => {
|
this.getPlayerAnimations(texture).forEach(d => {
|
||||||
this.scene.anims.create({
|
this.scene.anims.create({
|
||||||
@ -225,7 +240,84 @@ export abstract class Character extends Container {
|
|||||||
this.scene.sys.updateList.remove(sprite);
|
this.scene.sys.updateList.remove(sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.list.forEach(objectContaining => objectContaining.destroy())
|
||||||
super.destroy();
|
super.destroy();
|
||||||
this.playerName.destroy();
|
}
|
||||||
|
|
||||||
|
playEmote(emoteKey: string) {
|
||||||
|
this.cancelPreviousEmote();
|
||||||
|
|
||||||
|
const scalingFactor = waScaleManager.uiScalingFactor * 0.05;
|
||||||
|
const emoteY = -30 - scalingFactor * 10;
|
||||||
|
|
||||||
|
this.playerName.setVisible(false);
|
||||||
|
this.emote = new Sprite(this.scene, 0, 0, emoteKey);
|
||||||
|
this.emote.setAlpha(0);
|
||||||
|
this.emote.setScale(0.1 * scalingFactor);
|
||||||
|
this.add(this.emote);
|
||||||
|
this.scene.sys.updateList.add(this.emote);
|
||||||
|
|
||||||
|
this.createStartTransition(scalingFactor, emoteY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createStartTransition(scalingFactor: number, emoteY: number) {
|
||||||
|
this.emoteTween = this.scene.tweens.add({
|
||||||
|
targets: this.emote,
|
||||||
|
props: {
|
||||||
|
scale: scalingFactor,
|
||||||
|
alpha: 1,
|
||||||
|
y: emoteY,
|
||||||
|
},
|
||||||
|
ease: 'Power2',
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => {
|
||||||
|
this.startPulseTransition(emoteY, scalingFactor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private startPulseTransition(emoteY: number, scalingFactor: number) {
|
||||||
|
this.emoteTween = this.scene.tweens.add({
|
||||||
|
targets: this.emote,
|
||||||
|
props: {
|
||||||
|
y: emoteY * 1.3,
|
||||||
|
scale: scalingFactor * 1.1
|
||||||
|
},
|
||||||
|
duration: 250,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: 1,
|
||||||
|
completeDelay: 200,
|
||||||
|
onComplete: () => {
|
||||||
|
this.startExitTransition(emoteY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private startExitTransition(emoteY: number) {
|
||||||
|
this.emoteTween = this.scene.tweens.add({
|
||||||
|
targets: this.emote,
|
||||||
|
props: {
|
||||||
|
alpha: 0,
|
||||||
|
y: 2 * emoteY,
|
||||||
|
},
|
||||||
|
ease: 'Power2',
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => {
|
||||||
|
this.destroyEmote();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelPreviousEmote() {
|
||||||
|
if (!this.emote) return;
|
||||||
|
|
||||||
|
this.emoteTween?.remove();
|
||||||
|
this.destroyEmote()
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyEmote() {
|
||||||
|
this.emote?.destroy();
|
||||||
|
this.emote = null;
|
||||||
|
this.playerName.setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
import TextureManager = Phaser.Textures.TextureManager;
|
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
||||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
|
||||||
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
||||||
|
|
||||||
|
export interface FrameConfig {
|
||||||
|
frameWidth: number,
|
||||||
|
frameHeight: number,
|
||||||
|
}
|
||||||
|
|
||||||
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
||||||
const returnArray:BodyResourceDescriptionInterface[][] = [];
|
const returnArray:BodyResourceDescriptionInterface[][] = [];
|
||||||
@ -27,7 +30,10 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
|||||||
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
||||||
const name = 'customCharacterTexture'+texture.id;
|
const name = 'customCharacterTexture'+texture.id;
|
||||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
||||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
|
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||||
|
frameWidth: 32,
|
||||||
|
frameHeight: 32
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||||
@ -37,7 +43,10 @@ export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, textur
|
|||||||
//TODO refactor
|
//TODO refactor
|
||||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||||
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, {
|
||||||
|
frameWidth: 32,
|
||||||
|
frameHeight: 32
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}catch (err){
|
}catch (err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -62,7 +71,7 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
|
|||||||
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
|
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
|
||||||
const playerResource = PLAYER_RESOURCES[textureName];
|
const playerResource = PLAYER_RESOURCES[textureName];
|
||||||
if (playerResource !== undefined) return playerResource;
|
if (playerResource !== undefined) return playerResource;
|
||||||
|
|
||||||
for (let i=0; i<LAYERS.length;i++) {
|
for (let i=0; i<LAYERS.length;i++) {
|
||||||
const playerResource = LAYERS[i][textureName];
|
const playerResource = LAYERS[i][textureName];
|
||||||
if (playerResource !== undefined) return playerResource;
|
if (playerResource !== undefined) return playerResource;
|
||||||
@ -70,15 +79,12 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
|
|||||||
throw 'Could not find a data for texture '+textureName;
|
throw 'Could not find a data for texture '+textureName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
|
export const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig) => {
|
||||||
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
||||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
return res(playerResourceDescriptor);
|
return res(playerResourceDescriptor);
|
||||||
}
|
}
|
||||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
|
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||||
frameWidth: 32,
|
|
||||||
frameHeight: 32
|
|
||||||
});
|
|
||||||
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {GameScene} from "../Game/GameScene";
|
import type {GameScene} from "../Game/GameScene";
|
||||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||||
import {Character} from "../Entity/Character";
|
import {Character} from "../Entity/Character";
|
||||||
import {PlayerAnimationDirections} from "../Player/Animation";
|
import type {PlayerAnimationDirections} from "../Player/Animation";
|
||||||
|
|
||||||
|
export const playerClickedEvent = 'playerClickedEvent';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||||
@ -21,25 +23,29 @@ export class RemotePlayer extends Character {
|
|||||||
companion: string|null,
|
companion: string|null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||||
|
|
||||||
//set data
|
//set data
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
this.on('pointerdown', () => {
|
||||||
this.addCompanion(companion, companionTexturePromise);
|
this.emit(playerClickedEvent, this.userId);
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
updatePosition(position: PointInterface): void {
|
||||||
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
||||||
this.setX(position.x);
|
this.setX(position.x);
|
||||||
this.setY(position.y);
|
this.setY(position.y);
|
||||||
|
|
||||||
this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen).
|
this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen).
|
||||||
|
|
||||||
if (this.companion) {
|
if (this.companion) {
|
||||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isClickable(): boolean {
|
||||||
|
return true; //todo: make remote players clickable if they are logged in.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Scene = Phaser.Scene;
|
import Scene = Phaser.Scene;
|
||||||
import {Character} from "./Character";
|
import type {Character} from "./Character";
|
||||||
|
|
||||||
//todo: improve this WIP
|
//todo: improve this WIP
|
||||||
export class SpeechBubble {
|
export class SpeechBubble {
|
||||||
private bubble: Phaser.GameObjects.Graphics;
|
private bubble: Phaser.GameObjects.Graphics;
|
||||||
private content: Phaser.GameObjects.Text;
|
private content: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
|
|
||||||
constructor(scene: Scene, player: Character, text: string = "") {
|
constructor(scene: Scene, player: Character, text: string = "") {
|
||||||
|
|
||||||
const bubbleHeight = 50;
|
const bubbleHeight = 50;
|
||||||
|