Merge branch 'develop' of https://github.com/thecodingmachine/workadventure into iframe-api-refactor
@ -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=
|
||||||
|
15
CHANGELOG.md
@ -8,12 +8,17 @@
|
|||||||
|
|
||||||
### Updates
|
### 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.
|
||||||
- Mobile support has been improved
|
- 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
|
- 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
|
- Mouse wheel support to zoom in / out
|
||||||
- Pinch support on mobile to zoom in / out
|
- Pinch support on mobile to zoom in / out
|
||||||
- Improved virtual joystick size (adapts to the zoom level)
|
- Improved virtual joystick size (adapts to the zoom level)
|
||||||
|
- New scripting API features:
|
||||||
|
- Use `WA.loadSound(): Sound` to load / play / stop a sound
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -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[] {
|
||||||
@ -325,4 +326,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,6 +5,7 @@ import {
|
|||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
AdminRoomMessage,
|
AdminRoomMessage,
|
||||||
BanMessage,
|
BanMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
@ -71,6 +72,8 @@ 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.hasSendusermessage()) {
|
}else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
if(sendUserMessage !== undefined) {
|
if(sendUserMessage !== undefined) {
|
||||||
|
@ -26,7 +26,8 @@ import {
|
|||||||
GroupLeftZoneMessage,
|
GroupLeftZoneMessage,
|
||||||
WorldFullWarningMessage,
|
WorldFullWarningMessage,
|
||||||
UserLeftZoneMessage,
|
UserLeftZoneMessage,
|
||||||
BanUserMessage, RefreshRoomMessage,
|
EmoteEventMessage,
|
||||||
|
BanUserMessage, RefreshRoomMessage, EmotePromptMessage,
|
||||||
} 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";
|
||||||
@ -67,6 +68,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 +265,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 +342,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 +762,13 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
|
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: ""
|
||||||
|
@ -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: ""
|
||||||
|
5
front/dist/index.tmpl.html
vendored
@ -38,6 +38,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>
|
||||||
@ -71,7 +73,6 @@
|
|||||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="cowebsite" class="cowebsite hidden">
|
<div id="cowebsite" class="cowebsite hidden">
|
||||||
@ -106,7 +107,7 @@
|
|||||||
</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>
|
||||||
|
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 |
@ -4,21 +4,30 @@
|
|||||||
"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",
|
||||||
"@types/webpack-dev-server": "^3.11.4",
|
"@types/webpack-dev-server": "^3.11.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||||
"@typescript-eslint/parser": "^4.23.0",
|
"@typescript-eslint/parser": "^4.23.0",
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^5.2.4",
|
||||||
"eslint": "^7.26.0",
|
"eslint": "^7.26.0",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^6.2.9",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.3.1",
|
||||||
"jasmine": "^3.5.0",
|
"jasmine": "^3.5.0",
|
||||||
"mini-css-extract-plugin": "^1.6.0",
|
"mini-css-extract-plugin": "^1.6.0",
|
||||||
|
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||||
"sass": "^1.32.12",
|
"sass": "^1.32.12",
|
||||||
"sass-loader": "^11.1.0",
|
"sass-loader": "^11.1.0",
|
||||||
|
"svelte": "^3.38.2",
|
||||||
|
"svelte-loader": "^3.1.1",
|
||||||
|
"svelte-preprocess": "^4.7.3",
|
||||||
"ts-loader": "^9.1.2",
|
"ts-loader": "^9.1.2",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
|
"tsconfig-paths": "^3.9.0",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.2.4",
|
||||||
"webpack": "^5.37.0",
|
"webpack": "^5.37.0",
|
||||||
"webpack-cli": "^4.7.0",
|
"webpack-cli": "^4.7.0",
|
||||||
@ -39,9 +48,9 @@
|
|||||||
"socket.io-client": "^2.3.0"
|
"socket.io-client": "^2.3.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack serve --open",
|
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||||
"build": "NODE_ENV=production webpack",
|
"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"
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
|||||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
import type { OpenPopupEvent } from './OpenPopupEvent';
|
||||||
import type { OpenTabEvent } from './OpenTabEvent';
|
import type { OpenTabEvent } from './OpenTabEvent';
|
||||||
import type { UserInputChatEvent } from './UserInputChatEvent';
|
import type { UserInputChatEvent } from './UserInputChatEvent';
|
||||||
|
import type {LoadSoundEvent} from "./LoadSoundEvent";
|
||||||
|
import type {PlaySoundEvent} from "./PlaySoundEvent";
|
||||||
|
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
@ -29,6 +31,9 @@ export type IframeEventMap = {
|
|||||||
restorePlayerControls: null
|
restorePlayerControls: null
|
||||||
displayBubble: null
|
displayBubble: null
|
||||||
removeBubble: null
|
removeBubble: null
|
||||||
|
loadSound: LoadSoundEvent
|
||||||
|
playSound: PlaySoundEvent
|
||||||
|
stopSound: null
|
||||||
}
|
}
|
||||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||||
type: T;
|
type: T;
|
||||||
|
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,3 +1,4 @@
|
|||||||
|
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||||
@ -11,8 +12,9 @@ import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
|||||||
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||||
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
||||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
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.
|
||||||
@ -51,6 +53,15 @@ 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>();
|
||||||
|
|
||||||
@ -85,6 +96,15 @@ class IframeListener {
|
|||||||
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 === 'playSound' && isPlaySoundEvent(payload.data)) {
|
||||||
|
this._playSoundStream.next(payload.data);
|
||||||
|
}
|
||||||
|
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)) {
|
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||||
const scriptUrl = [...this.scripts.keys()].find(key => {
|
const scriptUrl = [...this.scripts.keys()].find(key => {
|
||||||
return this.scripts.get(key)?.contentWindow == message.source
|
return this.scripts.get(key)?.contentWindow == message.source
|
||||||
@ -92,9 +112,11 @@ class IframeListener {
|
|||||||
|
|
||||||
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (payload.type === 'closeCoWebSite') {
|
else if (payload.type === 'closeCoWebSite') {
|
||||||
scriptUtils.closeCoWebSite();
|
scriptUtils.closeCoWebSite();
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (payload.type === 'disablePlayerControls') {
|
else if (payload.type === 'disablePlayerControls') {
|
||||||
this._disablePlayerControlStream.next();
|
this._disablePlayerControlStream.next();
|
||||||
}
|
}
|
||||||
|
11
front/src/Components/App.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||||
|
import {menuIconVisible} from "../Stores/MenuStore";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<!-- {#if $menuIconVisible}
|
||||||
|
<MenuIcon />
|
||||||
|
{/if} -->
|
||||||
|
</div>
|
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>
|
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();
|
@ -27,6 +27,8 @@ import {
|
|||||||
SendJitsiJwtMessage,
|
SendJitsiJwtMessage,
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
PingMessage,
|
PingMessage,
|
||||||
|
EmoteEventMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
SendUserMessage,
|
SendUserMessage,
|
||||||
BanUserMessage
|
BanUserMessage
|
||||||
} from "../Messages/generated/messages_pb"
|
} from "../Messages/generated/messages_pb"
|
||||||
@ -47,6 +49,7 @@ 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";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -124,7 +127,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 +147,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;
|
||||||
@ -599,4 +607,14 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@ const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
|||||||
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
||||||
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
|
const PUSHER_URL = process.env.PUSHER_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;
|
||||||
@ -19,9 +20,10 @@ export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window
|
|||||||
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,
|
|
||||||
POSITION_DELAY,
|
POSITION_DELAY,
|
||||||
MAX_EXTRAPOLATION_TIME,
|
MAX_EXTRAPOLATION_TIME,
|
||||||
STUN_SERVER,
|
STUN_SERVER,
|
||||||
|
@ -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,5 +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";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
//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';
|
||||||
@ -19,8 +20,8 @@ export class MobileJoystick extends VirtualJoystick {
|
|||||||
x: -1000,
|
x: -1000,
|
||||||
y: -1000,
|
y: -1000,
|
||||||
radius: radius * window.devicePixelRatio,
|
radius: radius * window.devicePixelRatio,
|
||||||
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).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(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).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",
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,11 @@ 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,6 +19,8 @@ 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: BitmapText;
|
||||||
@ -23,15 +30,19 @@ export abstract class Character extends Container {
|
|||||||
//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;
|
||||||
@ -45,19 +56,18 @@ export abstract class Character extends Container {
|
|||||||
this.invisible = false
|
this.invisible = false
|
||||||
})
|
})
|
||||||
|
|
||||||
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
|
this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
|
||||||
this.teleportation.setInteractive();
|
this.playerName.setOrigin(0.5).setCenterAlign().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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
|||||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
import type {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[][] = [];
|
||||||
@ -26,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[]> => {
|
||||||
@ -36,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);
|
||||||
@ -69,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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,10 @@ 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.addCompanion(companion, companionTexturePromise);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
updatePosition(position: PointInterface): void {
|
||||||
@ -42,4 +38,8 @@ export class RemotePlayer extends Character {
|
|||||||
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 false; //todo: make remote players clickable if they are logged in.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
front/src/Phaser/Game/DepthIndexes.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//this file contains all the depth indexes which will be used in our game
|
||||||
|
|
||||||
|
export const DEPTH_TILE_INDEX = 0;
|
||||||
|
//Note: Player characters use their y coordinate as their depth to simulate a perspective.
|
||||||
|
//See the Character class.
|
||||||
|
export const DEPTH_OVERLAY_INDEX = 10000;
|
||||||
|
export const DEPTH_INGAME_TEXT_INDEX = 100000;
|
||||||
|
export const DEPTH_UI_INDEX = 1000000;
|
@ -3,6 +3,7 @@ import GameObject = Phaser.GameObjects.GameObject;
|
|||||||
import Events = Phaser.Scenes.Events;
|
import Events = Phaser.Scenes.Events;
|
||||||
import AnimationEvents = Phaser.Animations.Events;
|
import AnimationEvents = Phaser.Animations.Events;
|
||||||
import StructEvents = Phaser.Structs.Events;
|
import StructEvents = Phaser.Structs.Events;
|
||||||
|
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A scene that can track its dirty/pristine state.
|
* A scene that can track its dirty/pristine state.
|
||||||
@ -11,6 +12,7 @@ export abstract class DirtyScene extends ResizableScene {
|
|||||||
private isAlreadyTracking: boolean = false;
|
private isAlreadyTracking: boolean = false;
|
||||||
protected dirty:boolean = true;
|
protected dirty:boolean = true;
|
||||||
private objectListChanged:boolean = true;
|
private objectListChanged:boolean = true;
|
||||||
|
private physicsEnabled: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track all objects added to the scene and adds a callback each time an animation is added.
|
* Track all objects added to the scene and adds a callback each time an animation is added.
|
||||||
@ -19,7 +21,7 @@ export abstract class DirtyScene extends ResizableScene {
|
|||||||
* Note: this does not work with animations from sprites inside containers.
|
* Note: this does not work with animations from sprites inside containers.
|
||||||
*/
|
*/
|
||||||
protected trackDirtyAnims(): void {
|
protected trackDirtyAnims(): void {
|
||||||
if (this.isAlreadyTracking) {
|
if (this.isAlreadyTracking || SKIP_RENDER_OPTIMIZATIONS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isAlreadyTracking = true;
|
this.isAlreadyTracking = true;
|
||||||
@ -36,6 +38,27 @@ export abstract class DirtyScene extends ResizableScene {
|
|||||||
this.events.on(Events.RENDER, () => {
|
this.events.on(Events.RENDER, () => {
|
||||||
this.objectListChanged = false;
|
this.objectListChanged = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.physics.disableUpdate();
|
||||||
|
this.events.on(Events.POST_UPDATE, () => {
|
||||||
|
let objectMoving = false;
|
||||||
|
for (const body of this.physics.world.bodies.entries) {
|
||||||
|
if (body.velocity.x !== 0 || body.velocity.y !== 0) {
|
||||||
|
this.objectListChanged = true;
|
||||||
|
objectMoving = true;
|
||||||
|
if (!this.physicsEnabled) {
|
||||||
|
this.physics.enableUpdate();
|
||||||
|
this.physicsEnabled = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!objectMoving && this.physicsEnabled) {
|
||||||
|
this.physics.disableUpdate();
|
||||||
|
this.physicsEnabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private trackAnimation(): void {
|
private trackAnimation(): void {
|
||||||
|
73
front/src/Phaser/Game/EmoteManager.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||||
|
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
|
||||||
|
import type {GameScene} from "./GameScene";
|
||||||
|
import type {RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
|
import type {Subscription} from "rxjs";
|
||||||
|
|
||||||
|
|
||||||
|
interface RegisteredEmote extends BodyResourceDescriptionInterface {
|
||||||
|
name: string;
|
||||||
|
img: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emotes: {[key: string]: RegisteredEmote} = {
|
||||||
|
'emote-heart': {name: 'emote-heart', img: 'resources/emotes/heart-emote.png'},
|
||||||
|
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/clap-emote.png'},
|
||||||
|
'emote-hand': {name: 'emote-hand', img: 'resources/emotes/hand-emote.png'},
|
||||||
|
'emote-thanks': {name: 'emote-thanks', img: 'resources/emotes/thanks-emote.png'},
|
||||||
|
'emote-thumb-up': {name: 'emote-thumb-up', img: 'resources/emotes/thumb-up-emote.png'},
|
||||||
|
'emote-thumb-down': {name: 'emote-thumb-down', img: 'resources/emotes/thumb-down-emote.png'},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EmoteManager {
|
||||||
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
constructor(private scene: GameScene) {
|
||||||
|
this.subscription = emoteEventStream.stream.subscribe((event) => {
|
||||||
|
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
||||||
|
if (actor) {
|
||||||
|
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => {
|
||||||
|
actor.playEmote(emoteKey);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
createLoadingPromise(loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) {
|
||||||
|
return new Promise<string>((res) => {
|
||||||
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
|
return res(playerResourceDescriptor.name);
|
||||||
|
}
|
||||||
|
loadPlugin.image(playerResourceDescriptor.name, playerResourceDescriptor.img);
|
||||||
|
loadPlugin.once('filecomplete-image-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor.name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
|
||||||
|
const emoteDescriptor = emotes[textureKey];
|
||||||
|
if (emoteDescriptor === undefined) {
|
||||||
|
throw 'Emote not found!';
|
||||||
|
}
|
||||||
|
const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor);
|
||||||
|
this.scene.load.start();
|
||||||
|
return loadPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
getMenuImages(): Promise<RadialMenuItem[]> {
|
||||||
|
const promises = [];
|
||||||
|
for (const key in emotes) {
|
||||||
|
const promise = this.lazyLoadEmoteTexture(key).then((textureKey) => {
|
||||||
|
return {
|
||||||
|
image: textureKey,
|
||||||
|
name: textureKey,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,8 @@
|
|||||||
|
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
||||||
|
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||||
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
import {ResizableScene} from "../Login/ResizableScene";
|
||||||
|
|
||||||
const Events = Phaser.Core.Events;
|
const Events = Phaser.Core.Events;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -5,8 +10,27 @@ const Events = Phaser.Core.Events;
|
|||||||
* It comes with an optimization to skip rendering.
|
* It comes with an optimization to skip rendering.
|
||||||
*
|
*
|
||||||
* Beware, the "step" function might vary in future versions of Phaser.
|
* Beware, the "step" function might vary in future versions of Phaser.
|
||||||
|
*
|
||||||
|
* It also automatically calls "onResize" on any scenes extending ResizableScene.
|
||||||
*/
|
*/
|
||||||
export class Game extends Phaser.Game {
|
export class Game extends Phaser.Game {
|
||||||
|
|
||||||
|
private _isDirty = false;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
|
||||||
|
super(GameConfig);
|
||||||
|
|
||||||
|
window.addEventListener('resize', (event) => {
|
||||||
|
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
||||||
|
for (const scene of this.scene.getScenes(true)) {
|
||||||
|
if (scene instanceof ResizableScene) {
|
||||||
|
scene.onResize(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public step(time: number, delta: number)
|
public step(time: number, delta: number)
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -35,7 +59,7 @@ export class Game extends Phaser.Game {
|
|||||||
eventEmitter.emit(Events.POST_STEP, time, delta);
|
eventEmitter.emit(Events.POST_STEP, time, delta);
|
||||||
|
|
||||||
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
|
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
|
||||||
if (this.isDirty()) {
|
if (SKIP_RENDER_OPTIMIZATIONS || this.isDirty()) {
|
||||||
const renderer = this.renderer;
|
const renderer = this.renderer;
|
||||||
|
|
||||||
// Run the Pre-render (clearing the canvas, setting background colors, etc)
|
// Run the Pre-render (clearing the canvas, setting background colors, etc)
|
||||||
@ -62,6 +86,11 @@ export class Game extends Phaser.Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isDirty(): boolean {
|
private isDirty(): boolean {
|
||||||
|
if (this._isDirty) {
|
||||||
|
this._isDirty = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through the scenes in forward order
|
// Loop through the scenes in forward order
|
||||||
for (let i = 0; i < this.scene.scenes.length; i++)
|
for (let i = 0; i < this.scene.scenes.length; i++)
|
||||||
{
|
{
|
||||||
@ -85,4 +114,11 @@ export class Game extends Phaser.Game {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the game as needing to be redrawn.
|
||||||
|
*/
|
||||||
|
public markDirty(): void {
|
||||||
|
this._isDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import type {
|
|||||||
PositionInterface,
|
PositionInterface,
|
||||||
RoomJoinedMessageInterface
|
RoomJoinedMessageInterface
|
||||||
} from "../../Connexion/ConnexionModels";
|
} from "../../Connexion/ConnexionModels";
|
||||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
import {hasMovedEventName, Player, requestEmoteEventName} from "../Player/Player";
|
||||||
import {
|
import {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
JITSI_PRIVATE_MODE,
|
JITSI_PRIVATE_MODE,
|
||||||
@ -52,6 +52,7 @@ import {mediaManager} from "../../WebRtc/MediaManager";
|
|||||||
import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
|
import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
|
||||||
import type {ActionableItem} from "../Items/ActionableItem";
|
import type {ActionableItem} from "../Items/ActionableItem";
|
||||||
import {UserInputManager} from "../UserInput/UserInputManager";
|
import {UserInputManager} from "../UserInput/UserInputManager";
|
||||||
|
import {soundManager} from "./SoundManager";
|
||||||
import type {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
import type {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||||
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
||||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
@ -90,7 +91,10 @@ import {TextUtils} from "../Components/TextUtils";
|
|||||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||||
import {PinchManager} from "../UserInput/PinchManager";
|
import {PinchManager} from "../UserInput/PinchManager";
|
||||||
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
|
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
|
||||||
|
import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
import {peerStore} from "../../Stores/PeerStore";
|
||||||
|
import {EmoteManager} from "./EmoteManager";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface|null,
|
initPosition: PointInterface|null,
|
||||||
@ -131,7 +135,7 @@ const defaultStartLayerName = 'start';
|
|||||||
|
|
||||||
export class GameScene extends DirtyScene implements CenterListener {
|
export class GameScene extends DirtyScene implements CenterListener {
|
||||||
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
||||||
CurrentPlayer!: CurrentGamerInterface;
|
CurrentPlayer!: Player;
|
||||||
MapPlayers!: Phaser.Physics.Arcade.Group;
|
MapPlayers!: Phaser.Physics.Arcade.Group;
|
||||||
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
||||||
Map!: Phaser.Tilemaps.Tilemap;
|
Map!: Phaser.Tilemaps.Tilemap;
|
||||||
@ -187,7 +191,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
private originalMapUrl: string|undefined;
|
private originalMapUrl: string|undefined;
|
||||||
private pinchManager: PinchManager|undefined;
|
private pinchManager: PinchManager|undefined;
|
||||||
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
||||||
private onVisibilityChangeCallback: () => void;
|
private emoteManager!: EmoteManager;
|
||||||
|
|
||||||
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
||||||
super({
|
super({
|
||||||
@ -207,7 +211,6 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
||||||
this.connectionAnswerPromiseResolve = resolve;
|
this.connectionAnswerPromiseResolve = resolve;
|
||||||
});
|
});
|
||||||
this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//hook preload scene
|
//hook preload scene
|
||||||
@ -225,6 +228,11 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
this.load.image(joystickBaseKey, joystickBaseImg);
|
this.load.image(joystickBaseKey, joystickBaseImg);
|
||||||
this.load.image(joystickThumbKey, joystickThumbImg);
|
this.load.image(joystickThumbKey, joystickThumbImg);
|
||||||
}
|
}
|
||||||
|
//todo: in an emote manager.
|
||||||
|
this.load.spritesheet('emote-music', 'resources/emotes/pipo-popupemotes005.png', {
|
||||||
|
frameHeight: 32,
|
||||||
|
frameWidth: 32,
|
||||||
|
});
|
||||||
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
||||||
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
||||||
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
|
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
|
||||||
@ -420,7 +428,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||||
depth = 10000;
|
depth = DEPTH_OVERLAY_INDEX;
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup') {
|
if (layer.type === 'objectgroup') {
|
||||||
for (const object of layer.objects) {
|
for (const object of layer.objects) {
|
||||||
@ -507,7 +515,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
this.emoteManager = new EmoteManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -612,6 +620,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
|
|
||||||
// When connection is performed, let's connect SimplePeer
|
// When connection is performed, let's connect SimplePeer
|
||||||
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
||||||
|
peerStore.connectToSimplePeer(this.simplePeer);
|
||||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
||||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||||
|
|
||||||
@ -629,7 +638,6 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
self.chatModeSprite.setVisible(false);
|
self.chatModeSprite.setVisible(false);
|
||||||
self.openChatIcon.setVisible(false);
|
self.openChatIcon.setVisible(false);
|
||||||
audioManager.restoreVolume();
|
audioManager.restoreVolume();
|
||||||
self.onVisibilityChange();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -867,9 +875,28 @@ ${escapedMessage}
|
|||||||
this.userInputManager.disableControls();
|
this.userInputManager.disableControls();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent)=>
|
||||||
|
{
|
||||||
|
const url = new URL(playSoundEvent.url, this.MapUrlFile);
|
||||||
|
soundManager.playSound(this.load,this.sound,url.toString(),playSoundEvent.config);
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent)=>
|
||||||
|
{
|
||||||
|
const url = new URL(stopSoundEvent.url, this.MapUrlFile);
|
||||||
|
soundManager.stopSound(this.sound,url.toString());
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent)=>
|
||||||
|
{
|
||||||
|
const url = new URL(loadSoundEvent.url, this.MapUrlFile);
|
||||||
|
soundManager.loadSound(this.load,this.sound,url.toString());
|
||||||
|
}))
|
||||||
|
|
||||||
this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{
|
this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{
|
||||||
this.userInputManager.restoreControls();
|
this.userInputManager.restoreControls();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let scriptedBubbleSprite : Sprite;
|
let scriptedBubbleSprite : Sprite;
|
||||||
this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(()=>{
|
this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(()=>{
|
||||||
scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white');
|
scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white');
|
||||||
@ -929,12 +956,11 @@ ${escapedMessage}
|
|||||||
this.messageSubscription?.unsubscribe();
|
this.messageSubscription?.unsubscribe();
|
||||||
this.userInputManager.destroy();
|
this.userInputManager.destroy();
|
||||||
this.pinchManager?.destroy();
|
this.pinchManager?.destroy();
|
||||||
|
this.emoteManager.destroy();
|
||||||
|
|
||||||
for(const iframeEvents of this.iframeSubscriptionList){
|
for(const iframeEvents of this.iframeSubscriptionList){
|
||||||
iframeEvents.unsubscribe();
|
iframeEvents.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeAllRemotePlayers(): void {
|
private removeAllRemotePlayers(): void {
|
||||||
@ -1042,10 +1068,10 @@ ${escapedMessage}
|
|||||||
}
|
}
|
||||||
|
|
||||||
//todo: push that into the gameManager
|
//todo: push that into the gameManager
|
||||||
private async loadNextGame(exitSceneIdentifier: string){
|
private loadNextGame(exitSceneIdentifier: string): void {
|
||||||
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
||||||
const room = new Room(roomId);
|
const room = new Room(roomId);
|
||||||
await gameManager.loadMap(room, this.scene);
|
gameManager.loadMap(room, this.scene).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private startUser(layer: ITiledMapTileLayer): PositionInterface {
|
private startUser(layer: ITiledMapTileLayer): PositionInterface {
|
||||||
@ -1087,7 +1113,6 @@ ${escapedMessage}
|
|||||||
}
|
}
|
||||||
|
|
||||||
createCollisionWithPlayer() {
|
createCollisionWithPlayer() {
|
||||||
this.physics.disableUpdate();
|
|
||||||
//add collision layer
|
//add collision layer
|
||||||
this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => {
|
this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => {
|
||||||
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
|
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
|
||||||
@ -1121,6 +1146,12 @@ ${escapedMessage}
|
|||||||
this.companion,
|
this.companion,
|
||||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||||
);
|
);
|
||||||
|
this.CurrentPlayer.on('pointerdown', () => {
|
||||||
|
this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements))
|
||||||
|
})
|
||||||
|
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
|
||||||
|
this.connection?.emitEmoteEvent(emoteKey);
|
||||||
|
})
|
||||||
}catch (err){
|
}catch (err){
|
||||||
if(err instanceof TextureError) {
|
if(err instanceof TextureError) {
|
||||||
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
||||||
@ -1221,17 +1252,7 @@ ${escapedMessage}
|
|||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
mediaManager.updateScene();
|
mediaManager.updateScene();
|
||||||
this.currentTick = time;
|
this.currentTick = time;
|
||||||
if (this.CurrentPlayer.isMoving()) {
|
|
||||||
this.dirty = true;
|
|
||||||
}
|
|
||||||
this.CurrentPlayer.moveUser(delta);
|
this.CurrentPlayer.moveUser(delta);
|
||||||
if (this.CurrentPlayer.isMoving()) {
|
|
||||||
this.dirty = true;
|
|
||||||
this.physics.enableUpdate();
|
|
||||||
} else {
|
|
||||||
this.physics.disableUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Let's handle all events
|
// Let's handle all events
|
||||||
while (this.pendingEvents.length !== 0) {
|
while (this.pendingEvents.length !== 0) {
|
||||||
@ -1499,8 +1520,6 @@ ${escapedMessage}
|
|||||||
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
|
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
|
||||||
this.stopJitsi();
|
this.stopJitsi();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.onVisibilityChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopJitsi(): void {
|
public stopJitsi(): void {
|
||||||
@ -1509,7 +1528,6 @@ ${escapedMessage}
|
|||||||
mediaManager.showGameOverlay();
|
mediaManager.showGameOverlay();
|
||||||
|
|
||||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||||
this.onVisibilityChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||||
@ -1549,20 +1567,4 @@ ${escapedMessage}
|
|||||||
waScaleManager.zoomModifier *= zoomFactor;
|
waScaleManager.zoomModifier *= zoomFactor;
|
||||||
this.updateCameraOffset();
|
this.updateCameraOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onVisibilityChange(): void {
|
|
||||||
// If the overlay is not displayed, we are in Jitsi. We don't need the webcam.
|
|
||||||
if (!mediaManager.isGameOverlayVisible()) {
|
|
||||||
mediaManager.blurCamera();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.visibilityState === 'visible') {
|
|
||||||
mediaManager.focusCamera();
|
|
||||||
} else {
|
|
||||||
if (this.simplePeer.getNbConnections() === 0) {
|
|
||||||
mediaManager.blurCamera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
37
front/src/Phaser/Game/SoundManager.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
|
import BaseSoundManager = Phaser.Sound.BaseSoundManager;
|
||||||
|
import BaseSound = Phaser.Sound.BaseSound;
|
||||||
|
import SoundConfig = Phaser.Types.Sound.SoundConfig;
|
||||||
|
|
||||||
|
class SoundManager {
|
||||||
|
private soundPromises : Map<string,Promise<BaseSound>> = new Map<string, Promise<Phaser.Sound.BaseSound>>();
|
||||||
|
public loadSound (loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string) : Promise<BaseSound> {
|
||||||
|
let soundPromise = this.soundPromises.get(soundUrl);
|
||||||
|
if (soundPromise !== undefined) {
|
||||||
|
return soundPromise;
|
||||||
|
}
|
||||||
|
soundPromise = new Promise<BaseSound>((res) => {
|
||||||
|
|
||||||
|
const sound = soundManager.get(soundUrl);
|
||||||
|
if (sound !== null) {
|
||||||
|
return res(sound);
|
||||||
|
}
|
||||||
|
loadPlugin.audio(soundUrl, soundUrl);
|
||||||
|
loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl)));
|
||||||
|
loadPlugin.start();
|
||||||
|
});
|
||||||
|
this.soundPromises.set(soundUrl,soundPromise);
|
||||||
|
return soundPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async playSound(loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string, config: SoundConfig|undefined) : Promise<void> {
|
||||||
|
const sound = await this.loadSound(loadPlugin,soundManager,soundUrl);
|
||||||
|
if (config === undefined) sound.play();
|
||||||
|
else sound.play(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopSound(soundManager : BaseSoundManager,soundUrl : string){
|
||||||
|
soundManager.get(soundUrl).stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const soundManager = new SoundManager();
|
@ -10,6 +10,14 @@ import {PinchManager} from "../UserInput/PinchManager";
|
|||||||
import Zone = Phaser.GameObjects.Zone;
|
import Zone = Phaser.GameObjects.Zone;
|
||||||
import { MenuScene } from "../Menu/MenuScene";
|
import { MenuScene } from "../Menu/MenuScene";
|
||||||
import {ResizableScene} from "./ResizableScene";
|
import {ResizableScene} from "./ResizableScene";
|
||||||
|
import {
|
||||||
|
audioConstraintStore,
|
||||||
|
enableCameraSceneVisibilityStore,
|
||||||
|
localStreamStore,
|
||||||
|
mediaStreamConstraintsStore,
|
||||||
|
videoConstraintStore
|
||||||
|
} from "../../Stores/MediaStore";
|
||||||
|
import type {Unsubscriber} from "svelte/store";
|
||||||
|
|
||||||
export const EnableCameraSceneName = "EnableCameraScene";
|
export const EnableCameraSceneName = "EnableCameraScene";
|
||||||
enum LoginTextures {
|
enum LoginTextures {
|
||||||
@ -40,6 +48,7 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
|
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
|
||||||
|
|
||||||
private mobileTapZone!: Zone;
|
private mobileTapZone!: Zone;
|
||||||
|
private localStreamStoreUnsubscriber!: Unsubscriber;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
@ -119,9 +128,20 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
|
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
|
||||||
|
|
||||||
const mediaPromise = mediaManager.getCamera();
|
this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => {
|
||||||
|
if (result.type === 'error') {
|
||||||
|
// TODO: proper handling of the error
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getDevices();
|
||||||
|
if (result.stream !== null) {
|
||||||
|
this.setupStream(result.stream);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*const mediaPromise = mediaManager.getCamera();
|
||||||
mediaPromise.then(this.getDevices.bind(this));
|
mediaPromise.then(this.getDevices.bind(this));
|
||||||
mediaPromise.then(this.setupStream.bind(this));
|
mediaPromise.then(this.setupStream.bind(this));*/
|
||||||
|
|
||||||
this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
|
this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
|
||||||
this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
|
this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
|
||||||
@ -133,6 +153,8 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
this.add.existing(this.soundMeterSprite);
|
this.add.existing(this.soundMeterSprite);
|
||||||
|
|
||||||
this.onResize();
|
this.onResize();
|
||||||
|
|
||||||
|
enableCameraSceneVisibilityStore.showEnableCameraScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
private previousCam(): void {
|
private previousCam(): void {
|
||||||
@ -140,7 +162,9 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cameraSelected--;
|
this.cameraSelected--;
|
||||||
mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
|
videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
|
||||||
|
|
||||||
|
//mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private nextCam(): void {
|
private nextCam(): void {
|
||||||
@ -148,8 +172,10 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cameraSelected++;
|
this.cameraSelected++;
|
||||||
|
videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
|
||||||
|
|
||||||
// TODO: the change of camera should be OBSERVED (reactive)
|
// TODO: the change of camera should be OBSERVED (reactive)
|
||||||
mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
|
//mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private previousMic(): void {
|
private previousMic(): void {
|
||||||
@ -157,7 +183,8 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.microphoneSelected--;
|
this.microphoneSelected--;
|
||||||
mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
|
audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
|
||||||
|
//mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private nextMic(): void {
|
private nextMic(): void {
|
||||||
@ -165,8 +192,9 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.microphoneSelected++;
|
this.microphoneSelected++;
|
||||||
|
audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
|
||||||
// TODO: the change of camera should be OBSERVED (reactive)
|
// TODO: the change of camera should be OBSERVED (reactive)
|
||||||
mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
|
//mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -260,15 +288,20 @@ export class EnableCameraScene extends ResizableScene {
|
|||||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||||
this.soundMeter.stop();
|
this.soundMeter.stop();
|
||||||
|
|
||||||
mediaManager.stopCamera();
|
enableCameraSceneVisibilityStore.hideEnableCameraScene();
|
||||||
mediaManager.stopMicrophone();
|
this.localStreamStoreUnsubscriber();
|
||||||
|
//mediaManager.stopCamera();
|
||||||
|
//mediaManager.stopMicrophone();
|
||||||
|
|
||||||
this.scene.sleep(EnableCameraSceneName)
|
this.scene.sleep(EnableCameraSceneName);
|
||||||
gameManager.goToStartingMap(this.scene);
|
gameManager.goToStartingMap(this.scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getDevices() {
|
private async getDevices() {
|
||||||
|
// TODO: switch this in a store.
|
||||||
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
this.microphonesList = [];
|
||||||
|
this.camerasList = [];
|
||||||
for (const mediaDeviceInfo of mediaDeviceInfos) {
|
for (const mediaDeviceInfo of mediaDeviceInfos) {
|
||||||
if (mediaDeviceInfo.kind === 'audioinput') {
|
if (mediaDeviceInfo.kind === 'audioinput') {
|
||||||
this.microphonesList.push(mediaDeviceInfo);
|
this.microphonesList.push(mediaDeviceInfo);
|
||||||
|
@ -2,6 +2,8 @@ import {mediaManager} from "../../WebRtc/MediaManager";
|
|||||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
import {DirtyScene} from "../Game/DirtyScene";
|
import {DirtyScene} from "../Game/DirtyScene";
|
||||||
|
import {get} from "svelte/store";
|
||||||
|
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
|
||||||
|
|
||||||
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
||||||
const helpCameraSettings = 'helpCameraSettings';
|
const helpCameraSettings = 'helpCameraSettings';
|
||||||
@ -41,7 +43,7 @@ export class HelpCameraSettingsScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!localUserStore.getHelpCameraSettingsShown() && (!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video)){
|
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
|
||||||
this.openHelpCameraSettingsOpened();
|
this.openHelpCameraSettingsOpened();
|
||||||
localUserStore.setHelpCameraSettingsShown();
|
localUserStore.setHelpCameraSettingsShown();
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import {connectionManager} from "../../Connexion/ConnectionManager";
|
|||||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||||
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
||||||
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
||||||
|
import {menuIconVisible} from "../../Stores/MenuStore";
|
||||||
|
import {videoConstraintStore} from "../../Stores/MediaStore";
|
||||||
|
|
||||||
export const MenuSceneName = 'MenuScene';
|
export const MenuSceneName = 'MenuScene';
|
||||||
const gameMenuKey = 'gameMenu';
|
const gameMenuKey = 'gameMenu';
|
||||||
@ -53,6 +55,7 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
menuIconVisible.set(true);
|
||||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
||||||
this.menuElement.setOrigin(0);
|
this.menuElement.setOrigin(0);
|
||||||
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||||
@ -322,7 +325,7 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
if (valueVideo !== this.videoQualityValue) {
|
if (valueVideo !== this.videoQualityValue) {
|
||||||
this.videoQualityValue = valueVideo;
|
this.videoQualityValue = valueVideo;
|
||||||
localUserStore.setVideoQualityValue(valueVideo);
|
localUserStore.setVideoQualityValue(valueVideo);
|
||||||
mediaManager.updateCameraQuality(valueVideo);
|
videoConstraintStore.setFrameRate(valueVideo);
|
||||||
}
|
}
|
||||||
this.closeGameQualityMenu();
|
this.closeGameQualityMenu();
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,17 @@ import {PlayerAnimationDirections} from "./Animation";
|
|||||||
import type {GameScene} from "../Game/GameScene";
|
import type {GameScene} from "../Game/GameScene";
|
||||||
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||||
import {Character} from "../Entity/Character";
|
import {Character} from "../Entity/Character";
|
||||||
|
import {userMovingStore} from "../../Stores/GameStore";
|
||||||
|
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
export interface CurrentGamerInterface extends Character{
|
export const requestEmoteEventName = "requestEmote";
|
||||||
moveUser(delta: number) : void;
|
|
||||||
say(text : string) : void;
|
|
||||||
isMoving(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Player extends Character implements CurrentGamerInterface {
|
export class Player extends Character {
|
||||||
private previousDirection: string = PlayerAnimationDirections.Down;
|
private previousDirection: string = PlayerAnimationDirections.Down;
|
||||||
private wasMoving: boolean = false;
|
private wasMoving: boolean = false;
|
||||||
|
private emoteMenu: RadialMenu|null = null;
|
||||||
|
private updateListener: () => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
Scene: GameScene,
|
Scene: GameScene,
|
||||||
@ -26,14 +26,18 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||||||
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);
|
||||||
|
|
||||||
//the current player model should be push away by other players to prevent conflict
|
//the current player model should be push away by other players to prevent conflict
|
||||||
this.getBody().setImmovable(false);
|
this.getBody().setImmovable(false);
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
this.updateListener = () => {
|
||||||
this.addCompanion(companion, companionTexturePromise);
|
if (this.emoteMenu) {
|
||||||
}
|
this.emoteMenu.x = this.x;
|
||||||
|
this.emoteMenu.y = this.y;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.scene.events.addListener('postupdate', this.updateListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveUser(delta: number): void {
|
moveUser(delta: number): void {
|
||||||
@ -83,9 +87,43 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||||||
this.previousDirection = direction;
|
this.previousDirection = direction;
|
||||||
}
|
}
|
||||||
this.wasMoving = moving;
|
this.wasMoving = moving;
|
||||||
|
userMovingStore.set(moving);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isMoving(): boolean {
|
public isMoving(): boolean {
|
||||||
return this.wasMoving;
|
return this.wasMoving;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openOrCloseEmoteMenu(emotes:RadialMenuItem[]) {
|
||||||
|
if(this.emoteMenu) {
|
||||||
|
this.closeEmoteMenu();
|
||||||
|
} else {
|
||||||
|
this.openEmoteMenu(emotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isClickable(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
openEmoteMenu(emotes:RadialMenuItem[]): void {
|
||||||
|
this.cancelPreviousEmote();
|
||||||
|
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)
|
||||||
|
this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => {
|
||||||
|
this.closeEmoteMenu();
|
||||||
|
this.emit(requestEmoteEventName, item.name);
|
||||||
|
this.playEmote(item.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEmoteMenu(): void {
|
||||||
|
if (!this.emoteMenu) return;
|
||||||
|
this.emoteMenu.destroy();
|
||||||
|
this.emoteMenu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.scene.events.removeListener('postupdate', this.updateListener);
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import ScaleManager = Phaser.Scale.ScaleManager;
|
|
||||||
|
|
||||||
interface Size {
|
interface Size {
|
||||||
width: number;
|
width: number;
|
||||||
@ -13,8 +12,7 @@ export class HdpiManager {
|
|||||||
* @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
|
* @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
|
||||||
* @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
|
* @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
|
||||||
*/
|
*/
|
||||||
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
|
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
|
||||||
@ -36,16 +34,12 @@ export class HdpiManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = 1;
|
const optimalZoomLevel = this.getOptimalZoomLevel(realPixelNumber);
|
||||||
|
|
||||||
while (realPixelNumber > this.minRecommendedGamePixelsNumber * i * i) {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has the canvas more pixels than the screen? This is forbidden
|
// Has the canvas more pixels than the screen? This is forbidden
|
||||||
if ((i - 1) * this._zoomModifier < 1) {
|
if (optimalZoomLevel * this._zoomModifier < 1) {
|
||||||
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
||||||
this._zoomModifier = 1 / (i - 1);
|
this._zoomModifier = 1 / optimalZoomLevel;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
game: {
|
game: {
|
||||||
@ -59,8 +53,8 @@ export class HdpiManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameWidth = Math.ceil(realPixelScreenSize.width / (i - 1) / this._zoomModifier);
|
const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier);
|
||||||
const gameHeight = Math.ceil(realPixelScreenSize.height / (i - 1) / this._zoomModifier);
|
const gameHeight = Math.ceil(realPixelScreenSize.height / optimalZoomLevel / this._zoomModifier);
|
||||||
|
|
||||||
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
|
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
|
||||||
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
|
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
|
||||||
@ -68,7 +62,7 @@ export class HdpiManager {
|
|||||||
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
|
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
|
||||||
|
|
||||||
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
||||||
this._zoomModifier = realPixelScreenSize.width / minGameWidth / (i - 1);
|
this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
game: {
|
game: {
|
||||||
@ -89,12 +83,24 @@ export class HdpiManager {
|
|||||||
height: gameHeight,
|
height: gameHeight,
|
||||||
},
|
},
|
||||||
real: {
|
real: {
|
||||||
width: Math.ceil(realPixelScreenSize.width / (i - 1)) * (i - 1),
|
width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel,
|
||||||
height: Math.ceil(realPixelScreenSize.height / (i - 1)) * (i - 1),
|
height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We only accept integer but we make an exception for 1.5
|
||||||
|
*/
|
||||||
|
private getOptimalZoomLevel(realPixelNumber: number): number {
|
||||||
|
const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
|
||||||
|
if (1.5 <= result && result < 2) {
|
||||||
|
return 1.5
|
||||||
|
} else {
|
||||||
|
return Math.floor(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public get zoomModifier(): number {
|
public get zoomModifier(): number {
|
||||||
return this._zoomModifier;
|
return this._zoomModifier;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import {HdpiManager} from "./HdpiManager";
|
import {HdpiManager} from "./HdpiManager";
|
||||||
import ScaleManager = Phaser.Scale.ScaleManager;
|
import ScaleManager = Phaser.Scale.ScaleManager;
|
||||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||||
|
import type {Game} from "../Game/Game";
|
||||||
|
|
||||||
|
|
||||||
class WaScaleManager {
|
class WaScaleManager {
|
||||||
private hdpiManager: HdpiManager;
|
private hdpiManager: HdpiManager;
|
||||||
private scaleManager!: ScaleManager;
|
private scaleManager!: ScaleManager;
|
||||||
|
private game!: Game;
|
||||||
|
private actualZoom: number = 1;
|
||||||
|
|
||||||
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||||
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setScaleManager(scaleManager: ScaleManager) {
|
public setGame(game: Game): void {
|
||||||
this.scaleManager = scaleManager;
|
this.scaleManager = game.scale;
|
||||||
|
this.game = game;
|
||||||
}
|
}
|
||||||
|
|
||||||
public applyNewSize() {
|
public applyNewSize() {
|
||||||
@ -25,6 +29,7 @@ class WaScaleManager {
|
|||||||
|
|
||||||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
|
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
|
||||||
|
|
||||||
|
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
||||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||||
|
|
||||||
@ -32,6 +37,8 @@ class WaScaleManager {
|
|||||||
const style = this.scaleManager.canvas.style;
|
const style = this.scaleManager.canvas.style;
|
||||||
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
|
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
|
||||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
|
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
|
||||||
|
|
||||||
|
this.game.markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get zoomModifier(): number {
|
public get zoomModifier(): number {
|
||||||
@ -42,6 +49,14 @@ class WaScaleManager {
|
|||||||
this.hdpiManager.zoomModifier = zoomModifier;
|
this.hdpiManager.zoomModifier = zoomModifier;
|
||||||
this.applyNewSize();
|
this.applyNewSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to scale back the ui components to counter-act the zoom.
|
||||||
|
*/
|
||||||
|
public get uiScalingFactor(): number {
|
||||||
|
return this.actualZoom > 1 ? 1 : 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const waScaleManager = new WaScaleManager(640*480, 196*196);
|
export const waScaleManager = new WaScaleManager(640*480, 196*196);
|
||||||
|
@ -173,7 +173,7 @@ export class UserInputManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.joystick.destroy();
|
this.joystick?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initMouseWheel() {
|
private initMouseWheel() {
|
||||||
|
3
front/src/Stores/GameStore.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { derived, writable, Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const userMovingStore = writable(false);
|
510
front/src/Stores/MediaStore.ts
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
import {derived, get, Readable, readable, writable, Writable} from "svelte/store";
|
||||||
|
import {peerStore} from "./PeerStore";
|
||||||
|
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||||
|
import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
|
||||||
|
import {userMovingStore} from "./GameStore";
|
||||||
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains the camera state requested by the user (on or off).
|
||||||
|
*/
|
||||||
|
function createRequestedCameraState() {
|
||||||
|
const { subscribe, set, update } = writable(true);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
enableWebcam: () => set(true),
|
||||||
|
disableWebcam: () => set(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains the microphone state requested by the user (on or off).
|
||||||
|
*/
|
||||||
|
function createRequestedMicrophoneState() {
|
||||||
|
const { subscribe, set, update } = writable(true);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
enableMicrophone: () => set(true),
|
||||||
|
disableMicrophone: () => set(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing whether the current page is visible or not.
|
||||||
|
*/
|
||||||
|
export const visibilityStore = readable(document.visibilityState === 'visible', function start(set) {
|
||||||
|
const onVisibilityChange = () => {
|
||||||
|
set(document.visibilityState === 'visible');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
|
||||||
|
return function stop() {
|
||||||
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains whether the game overlay is shown or not.
|
||||||
|
* Typically, the overlay is hidden when entering Jitsi meet.
|
||||||
|
*/
|
||||||
|
function createGameOverlayVisibilityStore() {
|
||||||
|
const { subscribe, set, update } = writable(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
showGameOverlay: () => set(true),
|
||||||
|
hideGameOverlay: () => set(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains whether the EnableCameraScene is shown or not.
|
||||||
|
*/
|
||||||
|
function createEnableCameraSceneVisibilityStore() {
|
||||||
|
const { subscribe, set, update } = writable(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
showEnableCameraScene: () => set(true),
|
||||||
|
hideEnableCameraScene: () => set(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestedCameraState = createRequestedCameraState();
|
||||||
|
export const requestedMicrophoneState = createRequestedMicrophoneState();
|
||||||
|
export const gameOverlayVisibilityStore = createGameOverlayVisibilityStore();
|
||||||
|
export const enableCameraSceneVisibilityStore = createEnableCameraSceneVisibilityStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains "true" if the webcam should be stopped for privacy reasons - i.e. if the the user left the the page while not in a discussion.
|
||||||
|
*/
|
||||||
|
function createPrivacyShutdownStore() {
|
||||||
|
let privacyEnabled = false;
|
||||||
|
|
||||||
|
const { subscribe, set, update } = writable(privacyEnabled);
|
||||||
|
|
||||||
|
visibilityStore.subscribe((isVisible) => {
|
||||||
|
if (!isVisible && get(peerStore).size === 0) {
|
||||||
|
privacyEnabled = true;
|
||||||
|
set(true);
|
||||||
|
}
|
||||||
|
if (isVisible) {
|
||||||
|
privacyEnabled = false;
|
||||||
|
set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
peerStore.subscribe((peers) => {
|
||||||
|
if (peers.size === 0 && get(visibilityStore) === false) {
|
||||||
|
privacyEnabled = true;
|
||||||
|
set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const privacyShutdownStore = createPrivacyShutdownStore();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing whether the webcam was enabled in the last 10 seconds
|
||||||
|
*/
|
||||||
|
const enabledWebCam10secondsAgoStore = readable(false, function start(set) {
|
||||||
|
let timeout: NodeJS.Timeout|null = null;
|
||||||
|
|
||||||
|
const unsubscribe = requestedCameraState.subscribe((enabled) => {
|
||||||
|
if (enabled === true) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
set(false);
|
||||||
|
}, 10000);
|
||||||
|
set(true);
|
||||||
|
} else {
|
||||||
|
set(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return function stop() {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing whether the webcam was enabled in the last 5 seconds
|
||||||
|
*/
|
||||||
|
const userMoved5SecondsAgoStore = readable(false, function start(set) {
|
||||||
|
let timeout: NodeJS.Timeout|null = null;
|
||||||
|
|
||||||
|
const unsubscribe = userMovingStore.subscribe((moving) => {
|
||||||
|
if (moving === true) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
set(true);
|
||||||
|
} else {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
set(false);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return function stop() {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing whether the mouse is getting close the bottom right corner.
|
||||||
|
*/
|
||||||
|
const mouseInBottomRight = readable(false, function start(set) {
|
||||||
|
let lastInBottomRight = false;
|
||||||
|
const gameDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game');
|
||||||
|
|
||||||
|
const detectInBottomRight = (event: MouseEvent) => {
|
||||||
|
const rect = gameDiv.getBoundingClientRect();
|
||||||
|
const inBottomRight = event.x - rect.left > rect.width * 3 / 4 && event.y - rect.top > rect.height * 3 / 4;
|
||||||
|
if (inBottomRight !== lastInBottomRight) {
|
||||||
|
lastInBottomRight = inBottomRight;
|
||||||
|
set(inBottomRight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', detectInBottomRight);
|
||||||
|
|
||||||
|
return function stop() {
|
||||||
|
document.removeEventListener('mousemove', detectInBottomRight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains "true" if the webcam should be stopped for energy efficiency reason - i.e. we are not moving and not in a conversation.
|
||||||
|
*/
|
||||||
|
export const cameraEnergySavingStore = derived([userMoved5SecondsAgoStore, peerStore, enabledWebCam10secondsAgoStore, mouseInBottomRight], ([$userMoved5SecondsAgoStore,$peerStore, $enabledWebCam10secondsAgoStore, $mouseInBottomRight]) => {
|
||||||
|
return !$mouseInBottomRight && !$userMoved5SecondsAgoStore && $peerStore.size === 0 && !$enabledWebCam10secondsAgoStore;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains video constraints.
|
||||||
|
*/
|
||||||
|
function createVideoConstraintStore() {
|
||||||
|
const { subscribe, set, update } = writable({
|
||||||
|
width: { min: 640, ideal: 1280, max: 1920 },
|
||||||
|
height: { min: 400, ideal: 720 },
|
||||||
|
frameRate: { ideal: localUserStore.getVideoQualityValue() },
|
||||||
|
facingMode: "user",
|
||||||
|
resizeMode: 'crop-and-scale',
|
||||||
|
aspectRatio: 1.777777778
|
||||||
|
} as MediaTrackConstraints);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
setDeviceId: (deviceId: string) => update((constraints) => {
|
||||||
|
constraints.deviceId = {
|
||||||
|
exact: deviceId
|
||||||
|
};
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
}),
|
||||||
|
setFrameRate: (frameRate: number) => update((constraints) => {
|
||||||
|
constraints.frameRate = { ideal: frameRate };
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const videoConstraintStore = createVideoConstraintStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains video constraints.
|
||||||
|
*/
|
||||||
|
function createAudioConstraintStore() {
|
||||||
|
const { subscribe, set, update } = writable({
|
||||||
|
//TODO: make these values configurable in the game settings menu and store them in localstorage
|
||||||
|
autoGainControl: false,
|
||||||
|
echoCancellation: true,
|
||||||
|
noiseSuppression: true
|
||||||
|
} as boolean|MediaTrackConstraints);
|
||||||
|
|
||||||
|
let selectedDeviceId = null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
setDeviceId: (deviceId: string) => update((constraints) => {
|
||||||
|
selectedDeviceId = deviceId;
|
||||||
|
|
||||||
|
if (typeof(constraints) === 'boolean') {
|
||||||
|
constraints = {}
|
||||||
|
}
|
||||||
|
constraints.deviceId = {
|
||||||
|
exact: selectedDeviceId
|
||||||
|
};
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const audioConstraintStore = createAudioConstraintStore();
|
||||||
|
|
||||||
|
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
|
||||||
|
let previousComputedVideoConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
let previousComputedAudioConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the media constraints we want to apply.
|
||||||
|
*/
|
||||||
|
export const mediaStreamConstraintsStore = derived(
|
||||||
|
[
|
||||||
|
requestedCameraState,
|
||||||
|
requestedMicrophoneState,
|
||||||
|
gameOverlayVisibilityStore,
|
||||||
|
enableCameraSceneVisibilityStore,
|
||||||
|
videoConstraintStore,
|
||||||
|
audioConstraintStore,
|
||||||
|
privacyShutdownStore,
|
||||||
|
cameraEnergySavingStore,
|
||||||
|
], (
|
||||||
|
[
|
||||||
|
$requestedCameraState,
|
||||||
|
$requestedMicrophoneState,
|
||||||
|
$gameOverlayVisibilityStore,
|
||||||
|
$enableCameraSceneVisibilityStore,
|
||||||
|
$videoConstraintStore,
|
||||||
|
$audioConstraintStore,
|
||||||
|
$privacyShutdownStore,
|
||||||
|
$cameraEnergySavingStore,
|
||||||
|
], set
|
||||||
|
) => {
|
||||||
|
|
||||||
|
let currentVideoConstraint: boolean|MediaTrackConstraints = $videoConstraintStore;
|
||||||
|
let currentAudioConstraint: boolean|MediaTrackConstraints = $audioConstraintStore;
|
||||||
|
|
||||||
|
if ($enableCameraSceneVisibilityStore) {
|
||||||
|
set({
|
||||||
|
video: currentVideoConstraint,
|
||||||
|
audio: currentAudioConstraint,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable webcam if the user requested so
|
||||||
|
if ($requestedCameraState === false) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable microphone if the user requested so
|
||||||
|
if ($requestedMicrophoneState === false) {
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable webcam and microphone when in a Jitsi
|
||||||
|
if ($gameOverlayVisibilityStore === false) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable webcam for privacy reasons (the game is not visible and we were talking to noone)
|
||||||
|
if ($privacyShutdownStore === true) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable webcam for energy reasons (the user is not moving and we are talking to noone)
|
||||||
|
if ($cameraEnergySavingStore === true) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's make the changes only if the new value is different from the old one.
|
||||||
|
if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) {
|
||||||
|
previousComputedVideoConstraint = currentVideoConstraint;
|
||||||
|
previousComputedAudioConstraint = currentAudioConstraint;
|
||||||
|
// Let's copy the objects.
|
||||||
|
if (typeof previousComputedVideoConstraint !== 'boolean') {
|
||||||
|
previousComputedVideoConstraint = {...previousComputedVideoConstraint};
|
||||||
|
}
|
||||||
|
if (typeof previousComputedAudioConstraint !== 'boolean') {
|
||||||
|
previousComputedAudioConstraint = {...previousComputedAudioConstraint};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's wait a little bit to avoid sending too many constraint changes.
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
set({
|
||||||
|
video: currentVideoConstraint,
|
||||||
|
audio: currentAudioConstraint,
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
video: false,
|
||||||
|
audio: false
|
||||||
|
} as MediaStreamConstraints);
|
||||||
|
|
||||||
|
export type LocalStreamStoreValue = StreamSuccessValue | StreamErrorValue;
|
||||||
|
|
||||||
|
interface StreamSuccessValue {
|
||||||
|
type: "success",
|
||||||
|
stream: MediaStream|null,
|
||||||
|
// The constraints that we got (and not the one that have been requested)
|
||||||
|
constraints: MediaStreamConstraints
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StreamErrorValue {
|
||||||
|
type: "error",
|
||||||
|
error: Error,
|
||||||
|
constraints: MediaStreamConstraints
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentStream : MediaStream|null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the camera from filming
|
||||||
|
*/
|
||||||
|
function stopCamera(): void {
|
||||||
|
if (currentStream) {
|
||||||
|
for (const track of currentStream.getVideoTracks()) {
|
||||||
|
track.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the microphone from listening
|
||||||
|
*/
|
||||||
|
function stopMicrophone(): void {
|
||||||
|
if (currentStream) {
|
||||||
|
for (const track of currentStream.getAudioTracks()) {
|
||||||
|
track.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the MediaStream object (or null if nothing requested, or Error if an error occurred)
|
||||||
|
*/
|
||||||
|
export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalStreamStoreValue>(mediaStreamConstraintsStore, ($mediaStreamConstraintsStore, set) => {
|
||||||
|
const constraints = { ...$mediaStreamConstraintsStore };
|
||||||
|
|
||||||
|
if (navigator.mediaDevices === undefined) {
|
||||||
|
if (window.location.protocol === 'http:') {
|
||||||
|
//throw new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.');
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'),
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//throw new Error('Unable to access your camera or microphone. Your browser is too old.');
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: new Error('Unable to access your camera or microphone. Your browser is too old.'),
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraints.audio === false) {
|
||||||
|
stopMicrophone();
|
||||||
|
}
|
||||||
|
if (constraints.video === false) {
|
||||||
|
stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraints.audio === false && constraints.video === false) {
|
||||||
|
currentStream = null;
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: null,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
stopMicrophone();
|
||||||
|
stopCamera();
|
||||||
|
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: currentStream,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
if (constraints.video !== false) {
|
||||||
|
console.info("Error. Unable to get microphone and/or camera access. Trying audio only.", $mediaStreamConstraintsStore, e);
|
||||||
|
// TODO: does it make sense to pop this error when retrying?
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: e,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
// Let's try without video constraints
|
||||||
|
requestedCameraState.disableWebcam();
|
||||||
|
} else {
|
||||||
|
console.info("Error. Unable to get microphone and/or camera access.", $mediaStreamConstraintsStore, e);
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: e,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*constraints.video = false;
|
||||||
|
if (constraints.audio === false) {
|
||||||
|
console.info("Error. Unable to get microphone and/or camera access.", $mediaStreamConstraintsStore, e);
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: e,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
// Let's make as if the user did not ask.
|
||||||
|
requestedCameraState.disableWebcam();
|
||||||
|
} else {
|
||||||
|
console.info("Error. Unable to get microphone and/or camera access. Trying audio only.", $mediaStreamConstraintsStore, e);
|
||||||
|
try {
|
||||||
|
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: currentStream,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (e2) {
|
||||||
|
console.info("Error. Unable to get microphone fallback access.", $mediaStreamConstraintsStore, e2);
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: e,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the real active media constrained (not the one requested by the user, but the one we got from the system)
|
||||||
|
*/
|
||||||
|
export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStreamStore) => {
|
||||||
|
return $localStreamStore.constraints;
|
||||||
|
});
|
||||||
|
|
3
front/src/Stores/MenuStore.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { derived, writable, Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const menuIconVisible = writable(false);
|
36
front/src/Stores/PeerStore.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { derived, writable, Writable } from "svelte/store";
|
||||||
|
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||||
|
import type {SimplePeer} from "../WebRtc/SimplePeer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains the camera state requested by the user (on or off).
|
||||||
|
*/
|
||||||
|
function createPeerStore() {
|
||||||
|
let users = new Map<number, UserSimplePeerInterface>();
|
||||||
|
|
||||||
|
const { subscribe, set, update } = writable(users);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
connectToSimplePeer: (simplePeer: SimplePeer) => {
|
||||||
|
users = new Map<number, UserSimplePeerInterface>();
|
||||||
|
set(users);
|
||||||
|
simplePeer.registerPeerConnectionListener({
|
||||||
|
onConnect(user: UserSimplePeerInterface) {
|
||||||
|
update(users => {
|
||||||
|
users.set(user.userId, user);
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDisconnect(userId: number) {
|
||||||
|
update(users => {
|
||||||
|
users.delete(userId);
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const peerStore = createPeerStore();
|
192
front/src/Stores/ScreenSharingStore.ts
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import {derived, get, Readable, readable, writable, Writable} from "svelte/store";
|
||||||
|
import {peerStore} from "./PeerStore";
|
||||||
|
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||||
|
import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
|
||||||
|
import {userMovingStore} from "./GameStore";
|
||||||
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
|
import {
|
||||||
|
audioConstraintStore, cameraEnergySavingStore,
|
||||||
|
enableCameraSceneVisibilityStore,
|
||||||
|
gameOverlayVisibilityStore, LocalStreamStoreValue, privacyShutdownStore,
|
||||||
|
requestedCameraState,
|
||||||
|
requestedMicrophoneState, videoConstraintStore
|
||||||
|
} from "./MediaStore";
|
||||||
|
|
||||||
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains the camera state requested by the user (on or off).
|
||||||
|
*/
|
||||||
|
function createRequestedScreenSharingState() {
|
||||||
|
const { subscribe, set, update } = writable(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
enableScreenSharing: () => set(true),
|
||||||
|
disableScreenSharing: () => set(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestedScreenSharingState = createRequestedScreenSharingState();
|
||||||
|
|
||||||
|
let currentStream : MediaStream|null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the camera from filming
|
||||||
|
*/
|
||||||
|
function stopScreenSharing(): void {
|
||||||
|
if (currentStream) {
|
||||||
|
for (const track of currentStream.getVideoTracks()) {
|
||||||
|
track.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousComputedVideoConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
let previousComputedAudioConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the media constraints we want to apply.
|
||||||
|
*/
|
||||||
|
export const screenSharingConstraintsStore = derived(
|
||||||
|
[
|
||||||
|
requestedScreenSharingState,
|
||||||
|
gameOverlayVisibilityStore,
|
||||||
|
peerStore,
|
||||||
|
], (
|
||||||
|
[
|
||||||
|
$requestedScreenSharingState,
|
||||||
|
$gameOverlayVisibilityStore,
|
||||||
|
$peerStore,
|
||||||
|
], set
|
||||||
|
) => {
|
||||||
|
|
||||||
|
let currentVideoConstraint: boolean|MediaTrackConstraints = true;
|
||||||
|
let currentAudioConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
|
||||||
|
// Disable screen sharing if the user requested so
|
||||||
|
if (!$requestedScreenSharingState) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable screen sharing when in a Jitsi
|
||||||
|
if (!$gameOverlayVisibilityStore) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable screen sharing if no peers
|
||||||
|
if ($peerStore.size === 0) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's make the changes only if the new value is different from the old one.
|
||||||
|
if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) {
|
||||||
|
previousComputedVideoConstraint = currentVideoConstraint;
|
||||||
|
previousComputedAudioConstraint = currentAudioConstraint;
|
||||||
|
// Let's copy the objects.
|
||||||
|
/*if (typeof previousComputedVideoConstraint !== 'boolean') {
|
||||||
|
previousComputedVideoConstraint = {...previousComputedVideoConstraint};
|
||||||
|
}
|
||||||
|
if (typeof previousComputedAudioConstraint !== 'boolean') {
|
||||||
|
previousComputedAudioConstraint = {...previousComputedAudioConstraint};
|
||||||
|
}*/
|
||||||
|
|
||||||
|
set({
|
||||||
|
video: currentVideoConstraint,
|
||||||
|
audio: currentAudioConstraint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
video: false,
|
||||||
|
audio: false
|
||||||
|
} as MediaStreamConstraints);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
|
||||||
|
*/
|
||||||
|
export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstraints>, LocalStreamStoreValue>(screenSharingConstraintsStore, ($screenSharingConstraintsStore, set) => {
|
||||||
|
const constraints = $screenSharingConstraintsStore;
|
||||||
|
|
||||||
|
if ($screenSharingConstraintsStore.video === false && $screenSharingConstraintsStore.audio === false) {
|
||||||
|
stopScreenSharing();
|
||||||
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: null,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentStreamPromise: Promise<MediaStream>;
|
||||||
|
if (navigator.getDisplayMedia) {
|
||||||
|
currentStreamPromise = navigator.getDisplayMedia({constraints});
|
||||||
|
} else if (navigator.mediaDevices.getDisplayMedia) {
|
||||||
|
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints});
|
||||||
|
} else {
|
||||||
|
stopScreenSharing();
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: new Error('Your browser does not support sharing screen'),
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
stopScreenSharing();
|
||||||
|
currentStream = await currentStreamPromise;
|
||||||
|
|
||||||
|
// If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view
|
||||||
|
for (const track of currentStream.getTracks()) {
|
||||||
|
track.onended = () => {
|
||||||
|
stopScreenSharing();
|
||||||
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
|
previousComputedVideoConstraint = false;
|
||||||
|
previousComputedAudioConstraint = false;
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: null,
|
||||||
|
constraints: {
|
||||||
|
video: false,
|
||||||
|
audio: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: currentStream,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
currentStream = null;
|
||||||
|
console.info("Error. Unable to share screen.", e);
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: e,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing whether the screen sharing button should be displayed or hidden.
|
||||||
|
*/
|
||||||
|
export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set) => {
|
||||||
|
if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) {
|
||||||
|
set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set($peerStore.size !== 0);
|
||||||
|
});
|
@ -1,6 +1,8 @@
|
|||||||
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
import {JITSI_URL} from "../Enum/EnvironmentVariable";
|
||||||
import {mediaManager} from "./MediaManager";
|
import {mediaManager} from "./MediaManager";
|
||||||
import {coWebsiteManager} from "./CoWebsiteManager";
|
import {coWebsiteManager} from "./CoWebsiteManager";
|
||||||
|
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||||
|
import {get} from "svelte/store";
|
||||||
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
interface jitsiConfigInterface {
|
interface jitsiConfigInterface {
|
||||||
@ -10,10 +12,9 @@ interface jitsiConfigInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getDefaultConfig = () : jitsiConfigInterface => {
|
const getDefaultConfig = () : jitsiConfigInterface => {
|
||||||
const constraints = mediaManager.getConstraintRequestedByUser();
|
|
||||||
return {
|
return {
|
||||||
startWithAudioMuted: !constraints.audio,
|
startWithAudioMuted: !get(requestedMicrophoneState),
|
||||||
startWithVideoMuted: constraints.video === false,
|
startWithVideoMuted: !get(requestedCameraState),
|
||||||
prejoinPageEnabled: false
|
prejoinPageEnabled: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +73,6 @@ class JitsiFactory {
|
|||||||
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
private audioCallback = this.onAudioChange.bind(this);
|
private audioCallback = this.onAudioChange.bind(this);
|
||||||
private videoCallback = this.onVideoChange.bind(this);
|
private videoCallback = this.onVideoChange.bind(this);
|
||||||
private previousConfigMeet! : jitsiConfigInterface;
|
|
||||||
private jitsiScriptLoaded: boolean = false;
|
private jitsiScriptLoaded: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,9 +83,6 @@ class JitsiFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
|
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
|
||||||
//save previous config
|
|
||||||
this.previousConfigMeet = getDefaultConfig();
|
|
||||||
|
|
||||||
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
||||||
// Jitsi meet external API maintains some data in local storage
|
// Jitsi meet external API maintains some data in local storage
|
||||||
// which is sent via the appData URL parameter when joining a
|
// which is sent via the appData URL parameter when joining a
|
||||||
@ -134,27 +131,22 @@ class JitsiFactory {
|
|||||||
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
||||||
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
||||||
this.jitsiApi?.dispose();
|
this.jitsiApi?.dispose();
|
||||||
|
|
||||||
//restore previous config
|
|
||||||
if(this.previousConfigMeet?.startWithAudioMuted){
|
|
||||||
await mediaManager.disableMicrophone();
|
|
||||||
}else{
|
|
||||||
await mediaManager.enableMicrophone();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.previousConfigMeet?.startWithVideoMuted){
|
|
||||||
await mediaManager.disableCamera();
|
|
||||||
}else{
|
|
||||||
await mediaManager.enableCamera();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAudioChange({muted}: {muted: boolean}): void {
|
private onAudioChange({muted}: {muted: boolean}): void {
|
||||||
this.previousConfigMeet.startWithAudioMuted = muted;
|
if (muted) {
|
||||||
|
requestedMicrophoneState.disableMicrophone();
|
||||||
|
} else {
|
||||||
|
requestedMicrophoneState.enableMicrophone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onVideoChange({muted}: {muted: boolean}): void {
|
private onVideoChange({muted}: {muted: boolean}): void {
|
||||||
this.previousConfigMeet.startWithVideoMuted = muted;
|
if (muted) {
|
||||||
|
requestedCameraState.disableWebcam();
|
||||||
|
} else {
|
||||||
|
requestedCameraState.enableWebcam();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadJitsiScript(domain: string): Promise<void> {
|
private async loadJitsiScript(domain: string): Promise<void> {
|
||||||
|
@ -5,10 +5,22 @@ import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
|||||||
import {localUserStore} from "../Connexion/LocalUserStore";
|
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||||
import type {UserSimplePeerInterface} from "./SimplePeer";
|
import type {UserSimplePeerInterface} from "./SimplePeer";
|
||||||
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
||||||
|
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
|
||||||
|
import {
|
||||||
|
gameOverlayVisibilityStore, localStreamStore,
|
||||||
|
mediaStreamConstraintsStore,
|
||||||
|
requestedCameraState,
|
||||||
|
requestedMicrophoneState
|
||||||
|
} from "../Stores/MediaStore";
|
||||||
|
import {
|
||||||
|
requestedScreenSharingState,
|
||||||
|
screenSharingAvailableStore,
|
||||||
|
screenSharingLocalStreamStore
|
||||||
|
} from "../Stores/ScreenSharingStore";
|
||||||
|
|
||||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
let videoConstraint: boolean|MediaTrackConstraints = {
|
const videoConstraint: boolean|MediaTrackConstraints = {
|
||||||
width: { min: 640, ideal: 1280, max: 1920 },
|
width: { min: 640, ideal: 1280, max: 1920 },
|
||||||
height: { min: 400, ideal: 720 },
|
height: { min: 400, ideal: 720 },
|
||||||
frameRate: { ideal: localUserStore.getVideoQualityValue() },
|
frameRate: { ideal: localUserStore.getVideoQualityValue() },
|
||||||
@ -30,7 +42,6 @@ export type ReportCallback = (message: string) => void;
|
|||||||
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
|
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
|
||||||
export type HelpCameraSettingsCallBack = () => void;
|
export type HelpCameraSettingsCallBack = () => void;
|
||||||
|
|
||||||
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
|
|
||||||
export class MediaManager {
|
export class MediaManager {
|
||||||
localStream: MediaStream|null = null;
|
localStream: MediaStream|null = null;
|
||||||
localScreenCapture: MediaStream|null = null;
|
localScreenCapture: MediaStream|null = null;
|
||||||
@ -46,10 +57,6 @@ export class MediaManager {
|
|||||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||||
//mySoundMeterElement: HTMLDivElement;
|
//mySoundMeterElement: HTMLDivElement;
|
||||||
private webrtcOutAudio: HTMLAudioElement;
|
private webrtcOutAudio: HTMLAudioElement;
|
||||||
constraintsMedia : MediaStreamConstraints = {
|
|
||||||
audio: audioConstraint,
|
|
||||||
video: videoConstraint
|
|
||||||
};
|
|
||||||
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
|
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
|
||||||
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||||
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
|
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
|
||||||
@ -60,11 +67,8 @@ export class MediaManager {
|
|||||||
private cinemaBtn: HTMLDivElement;
|
private cinemaBtn: HTMLDivElement;
|
||||||
private monitorBtn: HTMLDivElement;
|
private monitorBtn: HTMLDivElement;
|
||||||
|
|
||||||
private previousConstraint : MediaStreamConstraints;
|
|
||||||
private focused : boolean = true;
|
private focused : boolean = true;
|
||||||
|
|
||||||
private hasCamera = true;
|
|
||||||
|
|
||||||
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
|
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
|
||||||
|
|
||||||
private userInputManager?: UserInputManager;
|
private userInputManager?: UserInputManager;
|
||||||
@ -87,14 +91,12 @@ export class MediaManager {
|
|||||||
this.microphoneClose.style.display = "none";
|
this.microphoneClose.style.display = "none";
|
||||||
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
|
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableMicrophone();
|
requestedMicrophoneState.enableMicrophone();
|
||||||
//update tracking
|
|
||||||
});
|
});
|
||||||
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
|
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
|
||||||
this.microphone.addEventListener('click', (e: MouseEvent) => {
|
this.microphone.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableMicrophone();
|
requestedMicrophoneState.disableMicrophone();
|
||||||
//update tracking
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
|
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
|
||||||
@ -102,14 +104,12 @@ export class MediaManager {
|
|||||||
this.cinemaClose.style.display = "none";
|
this.cinemaClose.style.display = "none";
|
||||||
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
|
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableCamera();
|
requestedCameraState.enableWebcam();
|
||||||
//update tracking
|
|
||||||
});
|
});
|
||||||
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
|
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
|
||||||
this.cinema.addEventListener('click', (e: MouseEvent) => {
|
this.cinema.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableCamera();
|
requestedCameraState.disableWebcam();
|
||||||
//update tracking
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
|
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
|
||||||
@ -117,21 +117,20 @@ export class MediaManager {
|
|||||||
this.monitorClose.style.display = "block";
|
this.monitorClose.style.display = "block";
|
||||||
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableScreenSharing();
|
//this.enableScreenSharing();
|
||||||
//update tracking
|
requestedScreenSharingState.enableScreenSharing();
|
||||||
});
|
});
|
||||||
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
|
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
|
||||||
this.monitor.style.display = "none";
|
this.monitor.style.display = "none";
|
||||||
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableScreenSharing();
|
//this.disableScreenSharing();
|
||||||
//update tracking
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
|
||||||
this.pingCameraStatus();
|
this.pingCameraStatus();
|
||||||
|
|
||||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
//FIX ME SOUNDMETER: check stability of sound meter calculation
|
||||||
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
|
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
|
||||||
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
|
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
|
||||||
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
|
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
|
||||||
@ -139,37 +138,98 @@ export class MediaManager {
|
|||||||
|
|
||||||
//Check of ask notification navigator permission
|
//Check of ask notification navigator permission
|
||||||
this.getNotification();
|
this.getNotification();
|
||||||
|
|
||||||
|
localStreamStore.subscribe((result) => {
|
||||||
|
if (result.type === 'error') {
|
||||||
|
console.error(result.error);
|
||||||
|
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
|
||||||
|
this.showHelpCameraSettingsCallBack();
|
||||||
|
}, this.userInputManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.constraints.video !== false) {
|
||||||
|
HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide');
|
||||||
|
}/*
|
||||||
|
if (result.constraints.audio !== false) {
|
||||||
|
this.enableMicrophoneStyle();
|
||||||
|
} else {
|
||||||
|
this.disableMicrophoneStyle();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
this.localStream = result.stream;
|
||||||
|
this.myCamVideo.srcObject = this.localStream;
|
||||||
|
|
||||||
|
// TODO: migrate all listeners to the store directly.
|
||||||
|
this.triggerUpdatedLocalStreamCallbacks(result.stream);
|
||||||
|
});
|
||||||
|
|
||||||
|
requestedCameraState.subscribe((enabled) => {
|
||||||
|
if (enabled) {
|
||||||
|
this.enableCameraStyle();
|
||||||
|
} else {
|
||||||
|
this.disableCameraStyle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
requestedMicrophoneState.subscribe((enabled) => {
|
||||||
|
if (enabled) {
|
||||||
|
this.enableMicrophoneStyle();
|
||||||
|
} else {
|
||||||
|
this.disableMicrophoneStyle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//let screenSharingStream : MediaStream|null;
|
||||||
|
screenSharingLocalStreamStore.subscribe((result) => {
|
||||||
|
if (result.type === 'error') {
|
||||||
|
console.error(result.error);
|
||||||
|
layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => {
|
||||||
|
this.showHelpCameraSettingsCallBack();
|
||||||
|
}, this.userInputManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.stream !== null) {
|
||||||
|
this.enableScreenSharingStyle();
|
||||||
|
mediaManager.localScreenCapture = result.stream;
|
||||||
|
|
||||||
|
// TODO: migrate this out of MediaManager
|
||||||
|
this.triggerStartedScreenSharingCallbacks(result.stream);
|
||||||
|
|
||||||
|
//screenSharingStream = result.stream;
|
||||||
|
|
||||||
|
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = result.stream;
|
||||||
|
} else {
|
||||||
|
this.disableScreenSharingStyle();
|
||||||
|
this.removeActiveScreenSharingVideo('me');
|
||||||
|
|
||||||
|
// FIXME: we need the old stream that is being stopped!
|
||||||
|
if (this.localScreenCapture) {
|
||||||
|
this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture);
|
||||||
|
this.localScreenCapture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//screenSharingStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
screenSharingAvailableStore.subscribe((available) => {
|
||||||
|
if (available) {
|
||||||
|
document.querySelector('.btn-monitor')?.classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.btn-monitor')?.classList.add('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateScene(){
|
public updateScene(){
|
||||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
//FIX ME SOUNDMETER: check stability of sound meter calculation
|
||||||
//this.updateSoudMeter();
|
//this.updateSoudMeter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public blurCamera() {
|
|
||||||
if(!this.focused){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.focused = false;
|
|
||||||
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
|
||||||
this.disableCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the constraint that the user wants (independently of the visibility / jitsi state...)
|
|
||||||
*/
|
|
||||||
public getConstraintRequestedByUser(): MediaStreamConstraints {
|
|
||||||
return this.previousConstraint ?? this.constraintsMedia;
|
|
||||||
}
|
|
||||||
|
|
||||||
public focusCamera() {
|
|
||||||
if(this.focused){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.focused = true;
|
|
||||||
this.applyPreviousConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
||||||
this.updatedLocalStreamCallBacks.add(callback);
|
this.updatedLocalStreamCallBacks.add(callback);
|
||||||
}
|
}
|
||||||
@ -213,6 +273,8 @@ export class MediaManager {
|
|||||||
this.triggerCloseJitsiFrameButton();
|
this.triggerCloseJitsiFrameButton();
|
||||||
}
|
}
|
||||||
buttonCloseFrame.removeEventListener('click', functionTrigger);
|
buttonCloseFrame.removeEventListener('click', functionTrigger);
|
||||||
|
|
||||||
|
gameOverlayVisibilityStore.showGameOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public hideGameOverlay(): void {
|
public hideGameOverlay(): void {
|
||||||
@ -224,110 +286,8 @@ export class MediaManager {
|
|||||||
this.triggerCloseJitsiFrameButton();
|
this.triggerCloseJitsiFrameButton();
|
||||||
}
|
}
|
||||||
buttonCloseFrame.addEventListener('click', functionTrigger);
|
buttonCloseFrame.addEventListener('click', functionTrigger);
|
||||||
}
|
|
||||||
|
|
||||||
public isGameOverlayVisible(): boolean {
|
gameOverlayVisibilityStore.hideGameOverlay();
|
||||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
|
||||||
return gameOverlay.classList.contains('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateCameraQuality(value: number) {
|
|
||||||
this.enableCameraStyle();
|
|
||||||
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
|
|
||||||
newVideoConstraint.frameRate = {exact: value, ideal: value};
|
|
||||||
videoConstraint = newVideoConstraint;
|
|
||||||
this.constraintsMedia.video = videoConstraint;
|
|
||||||
this.getCamera().then((stream: MediaStream) => {
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async enableCamera() {
|
|
||||||
this.constraintsMedia.video = videoConstraint;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const stream = await this.getCamera()
|
|
||||||
//TODO show error message tooltip upper of camera button
|
|
||||||
//TODO message : please check camera permission of your navigator
|
|
||||||
if(stream.getVideoTracks().length === 0) {
|
|
||||||
throw new Error('Video track is empty, please check camera permission of your navigator')
|
|
||||||
}
|
|
||||||
this.enableCameraStyle();
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
this.disableCameraStyle();
|
|
||||||
this.stopCamera();
|
|
||||||
|
|
||||||
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
|
|
||||||
this.showHelpCameraSettingsCallBack();
|
|
||||||
}, this.userInputManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async disableCamera() {
|
|
||||||
this.disableCameraStyle();
|
|
||||||
this.stopCamera();
|
|
||||||
|
|
||||||
if (this.constraintsMedia.audio !== false) {
|
|
||||||
const stream = await this.getCamera();
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
|
||||||
} else {
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async enableMicrophone() {
|
|
||||||
this.constraintsMedia.audio = audioConstraint;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const stream = await this.getCamera();
|
|
||||||
|
|
||||||
//TODO show error message tooltip upper of camera button
|
|
||||||
//TODO message : please check microphone permission of your navigator
|
|
||||||
if (stream.getAudioTracks().length === 0) {
|
|
||||||
throw Error('Audio track is empty, please check microphone permission of your navigator')
|
|
||||||
}
|
|
||||||
this.enableMicrophoneStyle();
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
this.disableMicrophoneStyle();
|
|
||||||
|
|
||||||
layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
|
|
||||||
this.showHelpCameraSettingsCallBack();
|
|
||||||
}, this.userInputManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async disableMicrophone() {
|
|
||||||
this.disableMicrophoneStyle();
|
|
||||||
this.stopMicrophone();
|
|
||||||
|
|
||||||
if (this.constraintsMedia.video !== false) {
|
|
||||||
const stream = await this.getCamera();
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
|
||||||
} else {
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private applyPreviousConfig() {
|
|
||||||
this.constraintsMedia = this.previousConstraint;
|
|
||||||
if(!this.constraintsMedia.video){
|
|
||||||
this.disableCameraStyle();
|
|
||||||
}else{
|
|
||||||
this.enableCameraStyle();
|
|
||||||
}
|
|
||||||
if(!this.constraintsMedia.audio){
|
|
||||||
this.disableMicrophoneStyle()
|
|
||||||
}else{
|
|
||||||
this.enableMicrophoneStyle()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getCamera().then((stream: MediaStream) => {
|
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enableCameraStyle(){
|
private enableCameraStyle(){
|
||||||
@ -340,8 +300,6 @@ export class MediaManager {
|
|||||||
this.cinemaClose.style.display = "block";
|
this.cinemaClose.style.display = "block";
|
||||||
this.cinema.style.display = "none";
|
this.cinema.style.display = "none";
|
||||||
this.cinemaBtn.classList.add("disabled");
|
this.cinemaBtn.classList.add("disabled");
|
||||||
this.constraintsMedia.video = false;
|
|
||||||
this.myCamVideo.srcObject = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enableMicrophoneStyle(){
|
private enableMicrophoneStyle(){
|
||||||
@ -354,185 +312,18 @@ export class MediaManager {
|
|||||||
this.microphoneClose.style.display = "block";
|
this.microphoneClose.style.display = "block";
|
||||||
this.microphone.style.display = "none";
|
this.microphone.style.display = "none";
|
||||||
this.microphoneBtn.classList.add("disabled");
|
this.microphoneBtn.classList.add("disabled");
|
||||||
this.constraintsMedia.audio = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enableScreenSharing() {
|
private enableScreenSharingStyle(){
|
||||||
this.getScreenMedia().then((stream) => {
|
this.monitorClose.style.display = "none";
|
||||||
this.triggerStartedScreenSharingCallbacks(stream);
|
this.monitor.style.display = "block";
|
||||||
this.monitorClose.style.display = "none";
|
this.monitorBtn.classList.add("enabled");
|
||||||
this.monitor.style.display = "block";
|
|
||||||
this.monitorBtn.classList.add("enabled");
|
|
||||||
}, () => {
|
|
||||||
this.monitorClose.style.display = "block";
|
|
||||||
this.monitor.style.display = "none";
|
|
||||||
this.monitorBtn.classList.remove("enabled");
|
|
||||||
|
|
||||||
layoutManager.addInformation('warning', 'Screen sharing access denied. Click here and check navigators permissions.', () => {
|
|
||||||
this.showHelpCameraSettingsCallBack();
|
|
||||||
}, this.userInputManager);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private disableScreenSharing() {
|
private disableScreenSharingStyle(){
|
||||||
this.monitorClose.style.display = "block";
|
this.monitorClose.style.display = "block";
|
||||||
this.monitor.style.display = "none";
|
this.monitor.style.display = "none";
|
||||||
this.monitorBtn.classList.remove("enabled");
|
this.monitorBtn.classList.remove("enabled");
|
||||||
this.removeActiveScreenSharingVideo('me');
|
|
||||||
this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => {
|
|
||||||
track.stop();
|
|
||||||
});
|
|
||||||
if (this.localScreenCapture === null) {
|
|
||||||
console.warn('Weird: trying to remove a screen sharing that is not enabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const localScreenCapture = this.localScreenCapture;
|
|
||||||
this.getCamera().then((stream) => {
|
|
||||||
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
|
|
||||||
}).catch((err) => { //catch error get camera
|
|
||||||
console.error(err);
|
|
||||||
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
|
|
||||||
});
|
|
||||||
this.localScreenCapture = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//get screen
|
|
||||||
getScreenMedia() : Promise<MediaStream>{
|
|
||||||
try {
|
|
||||||
return this._startScreenCapture()
|
|
||||||
.then((stream: MediaStream) => {
|
|
||||||
this.localScreenCapture = stream;
|
|
||||||
|
|
||||||
// If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view
|
|
||||||
for (const track of stream.getTracks()) {
|
|
||||||
track.onended = () => {
|
|
||||||
this.disableScreenSharing();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
|
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = stream;
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
})
|
|
||||||
.catch((err: unknown) => {
|
|
||||||
console.error("Error => getScreenMedia => ", err);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}catch (err) {
|
|
||||||
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _startScreenCapture() {
|
|
||||||
if (navigator.getDisplayMedia) {
|
|
||||||
return navigator.getDisplayMedia({video: true});
|
|
||||||
} else if (navigator.mediaDevices.getDisplayMedia) {
|
|
||||||
return navigator.mediaDevices.getDisplayMedia({video: true});
|
|
||||||
} else {
|
|
||||||
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
|
|
||||||
reject("error sharing screen");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//get camera
|
|
||||||
async getCamera(): Promise<MediaStream> {
|
|
||||||
if (navigator.mediaDevices === undefined) {
|
|
||||||
if (window.location.protocol === 'http:') {
|
|
||||||
throw new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.');
|
|
||||||
} else {
|
|
||||||
throw new Error('Unable to access your camera or microphone. Your browser is too old.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getLocalStream().catch((err) => {
|
|
||||||
console.info('Error get camera, trying with video option at null =>', err);
|
|
||||||
this.disableCameraStyle();
|
|
||||||
this.stopCamera();
|
|
||||||
|
|
||||||
return this.getLocalStream().then((stream : MediaStream) => {
|
|
||||||
this.hasCamera = false;
|
|
||||||
return stream;
|
|
||||||
}).catch((err) => {
|
|
||||||
this.disableMicrophoneStyle();
|
|
||||||
console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//TODO resize remote cam
|
|
||||||
/*console.log(this.localStream.getTracks());
|
|
||||||
let videoMediaStreamTrack = this.localStream.getTracks().find((media : MediaStreamTrack) => media.kind === "video");
|
|
||||||
let {width, height} = videoMediaStreamTrack.getSettings();
|
|
||||||
console.info(`${width}x${height}`); // 6*/
|
|
||||||
}
|
|
||||||
|
|
||||||
private getLocalStream() : Promise<MediaStream> {
|
|
||||||
return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream : MediaStream) => {
|
|
||||||
this.localStream = stream;
|
|
||||||
this.myCamVideo.srcObject = this.localStream;
|
|
||||||
|
|
||||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
|
||||||
/*this.mySoundMeter = null;
|
|
||||||
if(this.constraintsMedia.audio){
|
|
||||||
this.mySoundMeter = new SoundMeter();
|
|
||||||
this.mySoundMeter.connectToSource(stream, new AudioContext());
|
|
||||||
}*/
|
|
||||||
return stream;
|
|
||||||
}).catch((err: Error) => {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the camera from filming
|
|
||||||
*/
|
|
||||||
public stopCamera(): void {
|
|
||||||
if (this.localStream) {
|
|
||||||
for (const track of this.localStream.getVideoTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the microphone from listening
|
|
||||||
*/
|
|
||||||
public stopMicrophone(): void {
|
|
||||||
if (this.localStream) {
|
|
||||||
for (const track of this.localStream.getAudioTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//this.mySoundMeter?.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
setCamera(id: string): Promise<MediaStream> {
|
|
||||||
let video = this.constraintsMedia.video;
|
|
||||||
if (typeof(video) === 'boolean' || video === undefined) {
|
|
||||||
video = {}
|
|
||||||
}
|
|
||||||
video.deviceId = {
|
|
||||||
exact: id
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.getCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMicrophone(id: string): Promise<MediaStream> {
|
|
||||||
let audio = this.constraintsMedia.audio;
|
|
||||||
if (typeof(audio) === 'boolean' || audio === undefined) {
|
|
||||||
audio = {}
|
|
||||||
}
|
|
||||||
audio.deviceId = {
|
|
||||||
exact: id
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.getCamera();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
||||||
@ -856,7 +647,7 @@ export class MediaManager {
|
|||||||
|
|
||||||
public getNotification(){
|
public getNotification(){
|
||||||
//Get notification
|
//Get notification
|
||||||
if (window.Notification && Notification.permission !== "granted") {
|
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
|
||||||
Notification.requestPermission().catch((err) => {
|
Notification.requestPermission().catch((err) => {
|
||||||
console.error(`Notification permission error`, err);
|
console.error(`Notification permission error`, err);
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,8 @@ import type {RoomConnection} from "../Connexion/RoomConnection";
|
|||||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||||
import {blackListManager} from "./BlackListManager";
|
import {blackListManager} from "./BlackListManager";
|
||||||
|
import {get} from "svelte/store";
|
||||||
|
import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||||
|
|
||||||
export interface UserSimplePeerInterface{
|
export interface UserSimplePeerInterface{
|
||||||
userId: number;
|
userId: number;
|
||||||
@ -82,11 +84,10 @@ export class SimplePeer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mediaManager.showGameOverlay();
|
mediaManager.showGameOverlay();
|
||||||
mediaManager.getCamera().finally(() => {
|
|
||||||
//receive message start
|
//receive message start
|
||||||
this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
|
this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
|
||||||
this.receiveWebrtcStart(message);
|
this.receiveWebrtcStart(message);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => {
|
this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => {
|
||||||
@ -344,8 +345,15 @@ export class SimplePeer {
|
|||||||
if (!PeerConnection) {
|
if (!PeerConnection) {
|
||||||
throw new Error('While adding media, cannot find user with ID ' + userId);
|
throw new Error('While adding media, cannot find user with ID ' + userId);
|
||||||
}
|
}
|
||||||
const localStream: MediaStream | null = mediaManager.localStream;
|
|
||||||
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
|
const result = get(localStreamStore);
|
||||||
|
|
||||||
|
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints})));
|
||||||
|
|
||||||
|
if (result.type === 'error') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const localStream: MediaStream | null = result.stream;
|
||||||
|
|
||||||
if(!localStream){
|
if(!localStream){
|
||||||
return;
|
return;
|
||||||
|
@ -5,6 +5,8 @@ import type {RoomConnection} from "../Connexion/RoomConnection";
|
|||||||
import {blackListManager} from "./BlackListManager";
|
import {blackListManager} from "./BlackListManager";
|
||||||
import type {Subscription} from "rxjs";
|
import type {Subscription} from "rxjs";
|
||||||
import type {UserSimplePeerInterface} from "./SimplePeer";
|
import type {UserSimplePeerInterface} from "./SimplePeer";
|
||||||
|
import {get} from "svelte/store";
|
||||||
|
import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
@ -191,7 +193,7 @@ export class VideoPeer extends Peer {
|
|||||||
private pushVideoToRemoteUser() {
|
private pushVideoToRemoteUser() {
|
||||||
try {
|
try {
|
||||||
const localStream: MediaStream | null = mediaManager.localStream;
|
const localStream: MediaStream | null = mediaManager.localStream;
|
||||||
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
|
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)})));
|
||||||
|
|
||||||
if(!localStream){
|
if(!localStream){
|
||||||
return;
|
return;
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import type { GoToPageEvent } from "./Api/Events/GoToPageEvent";
|
import type { GoToPageEvent } from "./Api/Events/GoToPageEvent";
|
||||||
import { IframeResponseEventMap, isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
|
import { IframeResponseEventMap, isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
|
||||||
|
import type { LoadSoundEvent } from "./Api/Events/LoadSoundEvent";
|
||||||
import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
|
import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
|
||||||
import type { OpenTabEvent } from "./Api/Events/OpenTabEvent";
|
import type { OpenTabEvent } from "./Api/Events/OpenTabEvent";
|
||||||
|
import type { PlaySoundEvent } from "./Api/Events/PlaySoundEvent";
|
||||||
|
import type { StopSoundEvent } from "./Api/Events/StopSoundEvent";
|
||||||
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
|
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
|
||||||
|
import SoundConfig = Phaser.Types.Sound.SoundConfig;
|
||||||
|
|
||||||
|
|
||||||
export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: {
|
export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: {
|
||||||
typeChecker: Function
|
typeChecker: Function
|
||||||
@ -30,6 +35,7 @@ type ObjectOfKey<Key extends ApiKeys, O = WorkadventureCommandClasses> = O exten
|
|||||||
|
|
||||||
type ShouldAddAttribute<Key extends ApiKeys> = ObjectWithKeyOfUnion<Key>;
|
type ShouldAddAttribute<Key extends ApiKeys> = ObjectWithKeyOfUnion<Key>;
|
||||||
|
|
||||||
|
|
||||||
type WorkadventureFunctions = { [K in ApiKeys]: ObjectWithKeyOfUnion<K> extends Function ? K : never }[ApiKeys]
|
type WorkadventureFunctions = { [K in ApiKeys]: ObjectWithKeyOfUnion<K> extends Function ? K : never }[ApiKeys]
|
||||||
|
|
||||||
type WorkadventureFunctionsFilteredByRoot = { [K in WorkadventureFunctions]: ObjectOfKey<K>["addMethodsAtRoot"] extends true ? K : never }[WorkadventureFunctions]
|
type WorkadventureFunctionsFilteredByRoot = { [K in WorkadventureFunctions]: ObjectOfKey<K>["addMethodsAtRoot"] extends true ? K : never }[WorkadventureFunctions]
|
||||||
@ -55,6 +61,7 @@ export interface WorkAdventureApi extends WorkAdventureApiFiles {
|
|||||||
restorePlayerControls(): void;
|
restorePlayerControls(): void;
|
||||||
displayBubble(): void;
|
displayBubble(): void;
|
||||||
removeBubble(): void;
|
removeBubble(): void;
|
||||||
|
loadSound(url: string): Sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -74,6 +81,41 @@ const userInputChatStream: Subject<UserInputChatEvent> = new Subject();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class Sound {
|
||||||
|
constructor(private url: string) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type": 'loadSound',
|
||||||
|
"data": {
|
||||||
|
url: this.url,
|
||||||
|
} as LoadSoundEvent
|
||||||
|
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
public play(config: SoundConfig) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type": 'playSound',
|
||||||
|
"data": {
|
||||||
|
url: this.url,
|
||||||
|
config
|
||||||
|
} as PlaySoundEvent
|
||||||
|
|
||||||
|
}, '*');
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
public stop() {
|
||||||
|
window.parent.postMessage({
|
||||||
|
"type": 'stopSound',
|
||||||
|
"data": {
|
||||||
|
url: this.url,
|
||||||
|
} as StopSoundEvent
|
||||||
|
|
||||||
|
}, '*');
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
window.WA = {
|
window.WA = {
|
||||||
disablePlayerControls(): void {
|
disablePlayerControls(): void {
|
||||||
window.parent.postMessage({ 'type': 'disablePlayerControls' }, '*');
|
window.parent.postMessage({ 'type': 'disablePlayerControls' }, '*');
|
||||||
@ -100,6 +142,10 @@ window.WA = {
|
|||||||
}, '*');
|
}, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadSound(url: string): Sound {
|
||||||
|
return new Sound(url);
|
||||||
|
},
|
||||||
|
|
||||||
goToPage(url: string): void {
|
goToPage(url: string): void {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
"type": 'goToPage',
|
"type": 'goToPage',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'phaser';
|
import 'phaser';
|
||||||
import GameConfig = Phaser.Types.Core.GameConfig;
|
import GameConfig = Phaser.Types.Core.GameConfig;
|
||||||
import "../dist/resources/style/index.scss";
|
import "../style/index.scss";
|
||||||
|
|
||||||
import {DEBUG_MODE, isMobile} from "./Enum/EnvironmentVariable";
|
import {DEBUG_MODE, isMobile} from "./Enum/EnvironmentVariable";
|
||||||
import {LoginScene} from "./Phaser/Login/LoginScene";
|
import {LoginScene} from "./Phaser/Login/LoginScene";
|
||||||
@ -21,6 +21,8 @@ import { SelectCharacterMobileScene } from './Phaser/Login/SelectCharacterMobile
|
|||||||
import {HdpiManager} from "./Phaser/Services/HdpiManager";
|
import {HdpiManager} from "./Phaser/Services/HdpiManager";
|
||||||
import {waScaleManager} from "./Phaser/Services/WaScaleManager";
|
import {waScaleManager} from "./Phaser/Services/WaScaleManager";
|
||||||
import {Game} from "./Phaser/Game/Game";
|
import {Game} from "./Phaser/Game/Game";
|
||||||
|
import App from './Components/App.svelte';
|
||||||
|
import {HtmlUtils} from "./WebRtc/HtmlUtils";
|
||||||
|
|
||||||
const {width, height} = coWebsiteManager.getGameSize();
|
const {width, height} = coWebsiteManager.getGameSize();
|
||||||
|
|
||||||
@ -127,19 +129,12 @@ const config: GameConfig = {
|
|||||||
//const game = new Phaser.Game(config);
|
//const game = new Phaser.Game(config);
|
||||||
const game = new Game(config);
|
const game = new Game(config);
|
||||||
|
|
||||||
waScaleManager.setScaleManager(game.scale);
|
waScaleManager.setGame(game);
|
||||||
|
|
||||||
window.addEventListener('resize', function (event) {
|
window.addEventListener('resize', function (event) {
|
||||||
coWebsiteManager.resetStyle();
|
coWebsiteManager.resetStyle();
|
||||||
|
|
||||||
waScaleManager.applyNewSize();
|
waScaleManager.applyNewSize();
|
||||||
|
|
||||||
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
|
||||||
for (const scene of game.scene.getScenes(true)) {
|
|
||||||
if (scene instanceof ResizableScene) {
|
|
||||||
scene.onResize(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
coWebsiteManager.onResize.subscribe(() => {
|
coWebsiteManager.onResize.subscribe(() => {
|
||||||
@ -147,3 +142,10 @@ coWebsiteManager.onResize.subscribe(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
iframeListener.init();
|
iframeListener.init();
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: HtmlUtils.getElementByIdOrFail('svelte-overlay'),
|
||||||
|
props: { },
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|
||||||
&.loading {
|
&.loading {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
background: gray;
|
background: gray;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -32,7 +32,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 25px;
|
height: 25px;
|
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
@ -1,9 +1,9 @@
|
|||||||
*{
|
*{
|
||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
cursor: url('/resources/logos/cursor_normal.png'), auto;
|
cursor: url('./images/cursor_normal.png'), auto;
|
||||||
}
|
}
|
||||||
* a, button, select{
|
* a, button, select{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
body{
|
body{
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -39,7 +39,7 @@ body .message-info.warning{
|
|||||||
position: relative;
|
position: relative;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
background-color: #00000099;
|
background-color: #00000099;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
.video-container i{
|
.video-container i{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -75,7 +75,7 @@ body .message-info.warning{
|
|||||||
|
|
||||||
.video-container button.report{
|
.video-container button.report{
|
||||||
display: block;
|
display: block;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
background: none;
|
background: none;
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
border: none;
|
border: none;
|
||||||
@ -108,7 +108,7 @@ body .message-info.warning{
|
|||||||
left: 5px;
|
left: 5px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ body .message-info.warning{
|
|||||||
left: 36px;
|
left: 36px;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
.video-container img.active {
|
.video-container img.active {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
@ -126,7 +126,7 @@ body .message-info.warning{
|
|||||||
|
|
||||||
.video-container video{
|
.video-container video{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container video:focus{
|
.video-container video:focus{
|
||||||
@ -143,6 +143,11 @@ body .message-info.warning{
|
|||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
border-radius: 15px 15px 15px 15px;
|
border-radius: 15px 15px 15px 15px;
|
||||||
max-height: 20%;
|
max-height: 20%;
|
||||||
|
transition: right 350ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-myCamVideo.hide {
|
||||||
|
right: -20vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
video#myCamVideo{
|
video#myCamVideo{
|
||||||
@ -196,17 +201,17 @@ video#myCamVideo{
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
width: 15vw;
|
width: 180px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-end;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
}
|
}
|
||||||
/*btn animation*/
|
/*btn animation*/
|
||||||
.btn-cam-action div{
|
.btn-cam-action div{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
/*position: absolute;*/
|
/*position: absolute;*/
|
||||||
border: solid 0px black;
|
border: solid 0px black;
|
||||||
width: 44px;
|
width: 44px;
|
||||||
@ -216,7 +221,6 @@ video#myCamVideo{
|
|||||||
border-radius: 48px;
|
border-radius: 48px;
|
||||||
transform: translateY(20px);
|
transform: translateY(20px);
|
||||||
transition-timing-function: ease-in-out;
|
transition-timing-function: ease-in-out;
|
||||||
margin-bottom: 20px;
|
|
||||||
margin: 0 4%;
|
margin: 0 4%;
|
||||||
}
|
}
|
||||||
.btn-cam-action div.disabled {
|
.btn-cam-action div.disabled {
|
||||||
@ -248,6 +252,12 @@ video#myCamVideo{
|
|||||||
transition: all .2s;
|
transition: all .2s;
|
||||||
/*right: 224px;*/
|
/*right: 224px;*/
|
||||||
}
|
}
|
||||||
|
.btn-monitor.hide {
|
||||||
|
transform: translateY(60px);
|
||||||
|
}
|
||||||
|
.btn-cam-action:hover .btn-monitor.hide{
|
||||||
|
transform: translateY(60px);
|
||||||
|
}
|
||||||
.btn-copy{
|
.btn-copy{
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transition: all .3s;
|
transition: all .3s;
|
||||||
@ -260,7 +270,7 @@ video#myCamVideo{
|
|||||||
top: calc(48px - 37px);
|
top: calc(48px - 37px);
|
||||||
left: calc(48px - 41px);
|
left: calc(48px - 41px);
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Spinner */
|
/* Spinner */
|
||||||
@ -346,6 +356,8 @@ video#myCamVideo{
|
|||||||
#myCamVideoSetup {
|
#myCamVideoSetup {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
.webrtcsetup.active{
|
.webrtcsetup.active{
|
||||||
display: block;
|
display: block;
|
||||||
@ -572,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
margin: 2%;
|
margin: 2%;
|
||||||
flex-basis: 96%;
|
flex-basis: 96%;
|
||||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s;
|
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
/*flex-shrink: 2;*/
|
/*flex-shrink: 2;*/
|
||||||
}
|
}
|
||||||
@ -590,7 +602,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
.sidebar > div {
|
.sidebar > div {
|
||||||
margin: 2%;
|
margin: 2%;
|
||||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
|
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
border-radius: 15px 15px 15px 15px;
|
border-radius: 15px 15px 15px 15px;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
@ -600,7 +612,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar > div video {
|
.sidebar > div video {
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Let's make sure videos are vertically centered if they need to be cropped */
|
/* Let's make sure videos are vertically centered if they need to be cropped */
|
||||||
@ -625,7 +637,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
margin: 1%;
|
margin: 1%;
|
||||||
max-height: 96%;
|
max-height: 96%;
|
||||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s;
|
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-mode > div:hover {
|
.chat-mode > div:hover {
|
||||||
@ -715,7 +727,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
transition: all .5s ease;
|
transition: all .5s ease;
|
||||||
transform: rotateY(0);
|
transform: rotateY(0);
|
||||||
@ -739,7 +751,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
|
|
||||||
.main-console div.console:hover,
|
.main-console div.console:hover,
|
||||||
.message-container div.clear:hover {
|
.message-container div.clear:hover {
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
top: calc(100% + 5px);
|
top: calc(100% + 5px);
|
||||||
transform: scale(1.2) translateY(3px);
|
transform: scale(1.2) translateY(3px);
|
||||||
}
|
}
|
||||||
@ -772,7 +784,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
transition: all .2s ease;
|
transition: all .2s ease;
|
||||||
}
|
}
|
||||||
.main-console .btn-action .btn:hover{
|
.main-console .btn-action .btn:hover{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
background-color: #ffda01;
|
background-color: #ffda01;
|
||||||
color: black;
|
color: black;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
@ -787,7 +799,7 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
|
|
||||||
.main-console .menu span {
|
.main-console .menu span {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-console .menu span.active {
|
.main-console .menu span.active {
|
||||||
@ -821,10 +833,10 @@ input[type=range]:focus::-ms-fill-upper {
|
|||||||
}
|
}
|
||||||
.main-console section div.upload label img{
|
.main-console section div.upload label img{
|
||||||
height: 150px;
|
height: 150px;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
.main-console section div.upload label img{
|
.main-console section div.upload label img{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -917,7 +929,7 @@ div.modal-report-user{
|
|||||||
right: 0;
|
right: 0;
|
||||||
left: auto;
|
left: auto;
|
||||||
top: 0;
|
top: 0;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
@ -936,7 +948,7 @@ div.modal-report-user{
|
|||||||
transition: all .2s ease;
|
transition: all .2s ease;
|
||||||
}
|
}
|
||||||
.modal-report-user button:hover{
|
.modal-report-user button:hover{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
background-color: #ffda01;
|
background-color: #ffda01;
|
||||||
color: black;
|
color: black;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
@ -979,7 +991,7 @@ div.modal-report-user{
|
|||||||
}
|
}
|
||||||
.discussion .active-btn{
|
.discussion .active-btn{
|
||||||
display: none;
|
display: none;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
background-color: #2d2d2dba;
|
background-color: #2d2d2dba;
|
||||||
@ -1008,7 +1020,7 @@ div.modal-report-user{
|
|||||||
right: 10px;
|
right: 10px;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
.discussion .close-btn img{
|
.discussion .close-btn img{
|
||||||
height: 15px;
|
height: 15px;
|
||||||
@ -1033,7 +1045,7 @@ div.modal-report-user{
|
|||||||
background-color: #ffffff69;
|
background-color: #ffffff69;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion .participants .participant:hover{
|
.discussion .participants .participant:hover{
|
||||||
@ -1066,7 +1078,7 @@ div.modal-report-user{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.discussion .participants .participant button.report-btn{
|
.discussion .participants .participant button.report-btn{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #2d2d2dba;
|
background-color: #2d2d2dba;
|
||||||
right: 34px;
|
right: 34px;
|
||||||
@ -1176,7 +1188,7 @@ div.action.danger{
|
|||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-out;
|
||||||
}
|
}
|
||||||
div.action p.action-body{
|
div.action p.action-body{
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
cursor: url('./images/cursor_pointer.png'), pointer;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #2d2d2dba;
|
background-color: #2d2d2dba;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -1225,3 +1237,11 @@ div.action.danger p.action-body{
|
|||||||
50% {bottom: 30px;}
|
50% {bottom: 30px;}
|
||||||
100% {bottom: 40px;}
|
100% {bottom: 40px;}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#svelte-overlay {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
@ -50,6 +50,6 @@ describe("Test HdpiManager", () => {
|
|||||||
const result = hdpiManager.getOptimalGameSize({ width: 1280, height: 768 });
|
const result = hdpiManager.getOptimalGameSize({ width: 1280, height: 768 });
|
||||||
expect(result.game.width).toEqual(1280);
|
expect(result.game.width).toEqual(1280);
|
||||||
expect(result.game.height).toEqual(768);
|
expect(result.game.height).toEqual(768);
|
||||||
expect(hdpiManager.zoomModifier).toEqual(1);
|
expect(hdpiManager.zoomModifier).toEqual(2 / 3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
9
front/tsconfig-for-jasmine.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["./src/**/*", "./tests/**/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"lib": ["es2015","dom"],
|
||||||
|
"types": ["svelte", "node"]
|
||||||
|
},
|
||||||
|
}
|
7
front/tsconfig-for-webpack.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"esModuleInterop": true
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
// "include": ["src/**/*", "webpack.config.ts"],
|
||||||
|
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/",
|
"outDir": "./dist/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "CommonJS",
|
//"module": "CommonJS",
|
||||||
|
"module": "ESNext",
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import {Configuration} from "webpack";
|
import type {Configuration} from "webpack";
|
||||||
import WebpackDevServer from "webpack-dev-server";
|
import type WebpackDevServer from "webpack-dev-server";
|
||||||
|
import path from 'path';
|
||||||
const path = require('path');
|
import webpack from 'webpack';
|
||||||
const webpack = require('webpack');
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
import sveltePreprocess from 'svelte-preprocess';
|
||||||
|
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||||
|
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin';
|
||||||
|
|
||||||
const mode = process.env.NODE_ENV ?? 'development';
|
const mode = process.env.NODE_ENV ?? 'development';
|
||||||
const isProduction = mode === 'production';
|
const isProduction = mode === 'production';
|
||||||
@ -33,17 +35,85 @@ module.exports = {
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
use: 'ts-loader',
|
//use: 'ts-loader',
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
|
exclude: /node_modules/,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader, {
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
//url: false,
|
||||||
|
sourceMap: true
|
||||||
|
}
|
||||||
|
}, 'sass-loader'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader,
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
//url: false,
|
||||||
|
sourceMap: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(html|svelte)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'svelte-loader',
|
||||||
|
options: {
|
||||||
|
compilerOptions: {
|
||||||
|
// Dev mode must be enabled for HMR to work!
|
||||||
|
dev: isDevelopment
|
||||||
|
},
|
||||||
|
emitCss: isProduction,
|
||||||
|
hotReload: isDevelopment,
|
||||||
|
hotOptions: {
|
||||||
|
// List of options and defaults: https://www.npmjs.com/package/svelte-loader-hot#usage
|
||||||
|
noPreserveState: false,
|
||||||
|
optimistic: true,
|
||||||
|
},
|
||||||
|
preprocess: sveltePreprocess({
|
||||||
|
scss: true,
|
||||||
|
sass: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4
|
||||||
|
// See: https://github.com/sveltejs/svelte-loader#usage
|
||||||
|
{
|
||||||
|
test: /node_modules\/svelte\/.*\.mjs$/,
|
||||||
|
resolve: {
|
||||||
|
fullySpecified: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(ttf|eot|svg|png|gif|jpg)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
type: 'asset'
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [ '.tsx', '.ts', '.js' ],
|
alias: {
|
||||||
|
svelte: path.resolve('node_modules', 'svelte')
|
||||||
|
},
|
||||||
|
extensions: [ '.tsx', '.ts', '.js', '.svelte' ],
|
||||||
|
mainFields: ['svelte', 'browser', 'module', 'main']
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: (pathData) => {
|
filename: (pathData) => {
|
||||||
@ -54,11 +124,14 @@ module.exports = {
|
|||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
publicPath: '/'
|
publicPath: '/'
|
||||||
},
|
},
|
||||||
/*externals:[
|
|
||||||
require('webpack-require-http')
|
|
||||||
],*/
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
new ForkTsCheckerWebpackPlugin({
|
||||||
|
eslint: {
|
||||||
|
files: './src/**/*.ts'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
|
||||||
new HtmlWebpackPlugin(
|
new HtmlWebpackPlugin(
|
||||||
{
|
{
|
||||||
template: './dist/index.tmpl.html.tmp',
|
template: './dist/index.tmpl.html.tmp',
|
||||||
@ -77,8 +150,11 @@ module.exports = {
|
|||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
Phaser: 'phaser'
|
Phaser: 'phaser'
|
||||||
}),
|
}),
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
new webpack.EnvironmentPlugin({
|
new webpack.EnvironmentPlugin({
|
||||||
'API_URL': null,
|
'API_URL': null,
|
||||||
|
'SKIP_RENDER_OPTIMIZATIONS': false,
|
||||||
|
'DISABLE_NOTIFICATIONS': false,
|
||||||
'PUSHER_URL': undefined,
|
'PUSHER_URL': undefined,
|
||||||
'UPLOADER_URL': null,
|
'UPLOADER_URL': null,
|
||||||
'ADMIN_URL': null,
|
'ADMIN_URL': null,
|
||||||
|
1011
front/yarn.lock
@ -5,6 +5,12 @@ var targetObjectTutoBubble ='Tutobubble';
|
|||||||
var targetObjectTutoChat ='tutoChat';
|
var targetObjectTutoChat ='tutoChat';
|
||||||
var targetObjectTutoExplanation ='tutoExplanation';
|
var targetObjectTutoExplanation ='tutoExplanation';
|
||||||
var popUpExplanation = undefined;
|
var popUpExplanation = undefined;
|
||||||
|
var enterSoundUrl = "webrtc-in.mp3";
|
||||||
|
var exitSoundUrl = "webrtc-out.mp3";
|
||||||
|
var soundConfig = {
|
||||||
|
volume : 0.2,
|
||||||
|
loop : false
|
||||||
|
}
|
||||||
function launchTuto (){
|
function launchTuto (){
|
||||||
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
|
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
|
||||||
{
|
{
|
||||||
@ -25,7 +31,8 @@ function launchTuto (){
|
|||||||
label: "Got it!",
|
label: "Got it!",
|
||||||
className : "success",callback:(popup2 => {
|
className : "success",callback:(popup2 => {
|
||||||
popup2.close();
|
popup2.close();
|
||||||
WA.restorePlayerControls();
|
WA.restorePlayerControl();
|
||||||
|
WA.loadSound(winSoundUrl).play(soundConfig);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -36,13 +43,14 @@ function launchTuto (){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
WA.disablePlayerControls();
|
WA.disablePlayerControl();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
WA.onEnterZone('popupZone', () => {
|
WA.onEnterZone('popupZone', () => {
|
||||||
WA.displayBubble();
|
WA.displayBubble();
|
||||||
|
WA.loadSound(enterSoundUrl).play(soundConfig);
|
||||||
if (!isFirstTimeTuto) {
|
if (!isFirstTimeTuto) {
|
||||||
isFirstTimeTuto = true;
|
isFirstTimeTuto = true;
|
||||||
launchTuto();
|
launchTuto();
|
||||||
@ -71,4 +79,5 @@ WA.onEnterZone('popupZone', () => {
|
|||||||
WA.onLeaveZone('popupZone', () => {
|
WA.onLeaveZone('popupZone', () => {
|
||||||
if (popUpExplanation !== undefined) popUpExplanation.close();
|
if (popUpExplanation !== undefined) popUpExplanation.close();
|
||||||
WA.removeBubble();
|
WA.removeBubble();
|
||||||
|
WA.loadSound(exitSoundUrl).play(soundConfig);
|
||||||
})
|
})
|
||||||
|
BIN
maps/Tuto/webrtc-in.mp3
Normal file
BIN
maps/Tuto/webrtc-out.mp3
Normal file
BIN
maps/tests/Audience.mp3
Normal file
44
maps/tests/SoundScript.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
var zonePlaySound = "PlaySound";
|
||||||
|
var zonePlaySoundLoop = "playSoundLoop";
|
||||||
|
var stopSound = "StopSound";
|
||||||
|
var loopConfig ={
|
||||||
|
volume : 0.5,
|
||||||
|
loop : true
|
||||||
|
}
|
||||||
|
var configBase = {
|
||||||
|
volume : 0.5,
|
||||||
|
loop : false
|
||||||
|
}
|
||||||
|
var enterSoundUrl = "webrtc-in.mp3";
|
||||||
|
var exitSoundUrl = "webrtc-out.mp3";
|
||||||
|
var winSoundUrl = "Win.ogg";
|
||||||
|
var enterSound;
|
||||||
|
var exitSound;
|
||||||
|
var winSound;
|
||||||
|
loadAllSounds();
|
||||||
|
winSound.play(configBase);
|
||||||
|
WA.onEnterZone(zonePlaySound, () => {
|
||||||
|
enterSound.play(configBase);
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onEnterZone(zonePlaySoundLoop, () => {
|
||||||
|
winSound.play(loopConfig);
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onLeaveZone(zonePlaySoundLoop, () => {
|
||||||
|
winSound.stop();
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.onEnterZone('popupZone', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
WA.onLeaveZone('popupZone', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
function loadAllSounds(){
|
||||||
|
winSound = WA.loadSound(winSoundUrl);
|
||||||
|
enterSound = WA.loadSound(enterSoundUrl);
|
||||||
|
exitSound = WA.loadSound(exitSoundUrl);
|
||||||
|
}
|
154
maps/tests/SoundTest.json
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":20,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":20,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":20,
|
||||||
|
"id":4,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":20,
|
||||||
|
"id":3,
|
||||||
|
"name":"playSound",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"zone",
|
||||||
|
"type":"string",
|
||||||
|
"value":"PlaySound"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":20,
|
||||||
|
"id":6,
|
||||||
|
"name":"playSoundLoop",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"zone",
|
||||||
|
"type":"string",
|
||||||
|
"value":"playSoundLoop"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":20,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":5,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":19.296875,
|
||||||
|
"id":2,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"text":"Play Sound",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":107.109375,
|
||||||
|
"x":258.4453125,
|
||||||
|
"y":197.018229166667
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":19.296875,
|
||||||
|
"id":3,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"text":"Bonjour Monde",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":107.109375,
|
||||||
|
"x":-348.221354166667,
|
||||||
|
"y":257.018229166667
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":55.296875,
|
||||||
|
"id":4,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"text":"Play Sound Loop\nexit Zone Stop Sound \n",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":176.442708333333,
|
||||||
|
"x":243.778645833333,
|
||||||
|
"y":368.3515625
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":8,
|
||||||
|
"nextobjectid":5,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"SoundScript.js"
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.5.0",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.5,
|
||||||
|
"width":20
|
||||||
|
}
|
BIN
maps/tests/Win.ogg
Normal file
@ -42,6 +42,14 @@
|
|||||||
<a href="#" class="testLink" data-testmap="script_api.json" target="_blank">Testing scripting API with a script</a>
|
<a href="#" class="testLink" data-testmap="script_api.json" target="_blank">Testing scripting API with a script</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="radio" name="test-scripting-sound"> Success <input type="radio" name="test-scripting-sound"> Failure <input type="radio" name="test-scripting-sound" checked> Pending
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="testLink" data-testmap="SoundTest.json" target="_blank">Testing scripting API loadSound() function</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<input type="radio" name="test-autoresize"> Success <input type="radio" name="test-autoresize"> Failure <input type="radio" name="test-autoresize" checked> Pending
|
<input type="radio" name="test-autoresize"> Success <input type="radio" name="test-autoresize"> Failure <input type="radio" name="test-autoresize" checked> Pending
|
||||||
|
BIN
maps/tests/webrtc-in.mp3
Normal file
BIN
maps/tests/webrtc-out.mp3
Normal file
@ -20,5 +20,8 @@
|
|||||||
|
|
||||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */
|
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -665,9 +665,9 @@ has@^1.0.3:
|
|||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
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==
|
||||||
|
|
||||||
iconv-lite@^0.4.24:
|
iconv-lite@^0.4.24:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
|
@ -66,6 +66,15 @@ message ReportPlayerMessage {
|
|||||||
string reportComment = 2;
|
string reportComment = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message EmotePromptMessage {
|
||||||
|
string emote = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EmoteEventMessage {
|
||||||
|
int32 actorUserId = 1;
|
||||||
|
string emote = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message QueryJitsiJwtMessage {
|
message QueryJitsiJwtMessage {
|
||||||
string jitsiRoom = 1;
|
string jitsiRoom = 1;
|
||||||
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
|
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
|
||||||
@ -84,6 +93,7 @@ message ClientToServerMessage {
|
|||||||
StopGlobalMessage stopGlobalMessage = 10;
|
StopGlobalMessage stopGlobalMessage = 10;
|
||||||
ReportPlayerMessage reportPlayerMessage = 11;
|
ReportPlayerMessage reportPlayerMessage = 11;
|
||||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
||||||
|
EmotePromptMessage emotePromptMessage = 13;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +132,7 @@ message SubMessage {
|
|||||||
UserJoinedMessage userJoinedMessage = 4;
|
UserJoinedMessage userJoinedMessage = 4;
|
||||||
UserLeftMessage userLeftMessage = 5;
|
UserLeftMessage userLeftMessage = 5;
|
||||||
ItemEventMessage itemEventMessage = 6;
|
ItemEventMessage itemEventMessage = 6;
|
||||||
|
EmoteEventMessage emoteEventMessage = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +258,7 @@ message ServerToClientMessage {
|
|||||||
WorldFullMessage worldFullMessage = 16;
|
WorldFullMessage worldFullMessage = 16;
|
||||||
RefreshRoomMessage refreshRoomMessage = 17;
|
RefreshRoomMessage refreshRoomMessage = 17;
|
||||||
WorldConnexionMessage worldConnexionMessage = 18;
|
WorldConnexionMessage worldConnexionMessage = 18;
|
||||||
|
EmoteEventMessage emoteEventMessage = 19;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +329,7 @@ message PusherToBackMessage {
|
|||||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
|
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
|
||||||
SendUserMessage sendUserMessage = 12;
|
SendUserMessage sendUserMessage = 12;
|
||||||
BanUserMessage banUserMessage = 13;
|
BanUserMessage banUserMessage = 13;
|
||||||
|
EmotePromptMessage emotePromptMessage = 14;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +347,7 @@ message SubToPusherMessage {
|
|||||||
ItemEventMessage itemEventMessage = 6;
|
ItemEventMessage itemEventMessage = 6;
|
||||||
SendUserMessage sendUserMessage = 7;
|
SendUserMessage sendUserMessage = 7;
|
||||||
BanUserMessage banUserMessage = 8;
|
BanUserMessage banUserMessage = 8;
|
||||||
|
EmoteEventMessage emoteEventMessage = 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2097,9 +2097,9 @@ highlight.js@^9.12.0:
|
|||||||
integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
|
integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
|
||||||
|
|
||||||
hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
|
hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
|
||||||
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==
|
||||||
|
|
||||||
html-tag@^2.0.0:
|
html-tag@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
ReportPlayerMessage,
|
ReportPlayerMessage,
|
||||||
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage
|
EmoteEventMessage,
|
||||||
|
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage, EmotePromptMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
||||||
import {TemplatedApp} from "uWebSockets.js"
|
import {TemplatedApp} from "uWebSockets.js"
|
||||||
@ -182,7 +183,7 @@ export class IoSocketController {
|
|||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
||||||
console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.');
|
console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.');
|
||||||
} else if(err?.response?.status == 403) {
|
} else if(err?.response?.status == 403) {
|
||||||
// If we get an HTTP 404, the world is full. We need to broadcast a special error to the client.
|
// If we get an HTTP 403, the world is full. We need to broadcast a special error to the client.
|
||||||
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
||||||
return res.upgrade({
|
return res.upgrade({
|
||||||
rejected: true,
|
rejected: true,
|
||||||
@ -330,6 +331,8 @@ export class IoSocketController {
|
|||||||
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()){
|
||||||
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||||
|
} else if (message.hasEmotepromptmessage()){
|
||||||
|
socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok is false if backpressure was built up, wait for drain */
|
/* Ok is false if backpressure was built up, wait for drain */
|
||||||
|
@ -6,13 +6,11 @@ import {
|
|||||||
PointMessage, PositionMessage, UserJoinedMessage,
|
PointMessage, PositionMessage, UserJoinedMessage,
|
||||||
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
|
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
|
||||||
ZoneMessage,
|
ZoneMessage,
|
||||||
|
EmoteEventMessage,
|
||||||
CompanionMessage
|
CompanionMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import * as messages_pb from "../Messages/generated/messages_pb";
|
|
||||||
import {ClientReadableStream} from "grpc";
|
import {ClientReadableStream} from "grpc";
|
||||||
import {PositionDispatcher} from "_Model/PositionDispatcher";
|
import {PositionDispatcher} from "_Model/PositionDispatcher";
|
||||||
import {socketManager} from "../Services/SocketManager";
|
|
||||||
import {ProtobufUtils} from "_Model/Websocket/ProtobufUtils";
|
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
|
|
||||||
const debug = Debug("zone");
|
const debug = Debug("zone");
|
||||||
@ -24,6 +22,7 @@ export interface ZoneEventListener {
|
|||||||
onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void;
|
onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void;
|
||||||
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
|
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
|
||||||
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
|
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
|
||||||
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export type EntersCallback = (thing: Movable, listener: User) => void;
|
/*export type EntersCallback = (thing: Movable, listener: User) => void;
|
||||||
@ -184,6 +183,9 @@ export class Zone {
|
|||||||
userDescriptor.update(userMovedMessage);
|
userDescriptor.update(userMovedMessage);
|
||||||
|
|
||||||
this.notifyUserMove(userDescriptor);
|
this.notifyUserMove(userDescriptor);
|
||||||
|
} else if(message.hasEmoteeventmessage()) {
|
||||||
|
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
|
this.notifyEmote(emoteEventMessage);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected message');
|
throw new Error('Unexpected message');
|
||||||
}
|
}
|
||||||
@ -262,6 +264,15 @@ export class Zone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private notifyEmote(emoteMessage: EmoteEventMessage) {
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
if (listener.userId === emoteMessage.getActoruserid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.socketListener.onEmote(emoteMessage, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this group left
|
* Notify listeners of this zone that this group left
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +23,8 @@ import {
|
|||||||
WorldConnexionMessage,
|
WorldConnexionMessage,
|
||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage
|
EmoteEventMessage,
|
||||||
|
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage, EmotePromptMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
||||||
@ -73,6 +74,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
client.adminConnection = adminRoomStream;
|
client.adminConnection = adminRoomStream;
|
||||||
|
|
||||||
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
||||||
|
|
||||||
if (message.hasUserjoinedroom()) {
|
if (message.hasUserjoinedroom()) {
|
||||||
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
||||||
if (!client.disconnecting) {
|
if (!client.disconnecting) {
|
||||||
@ -254,6 +256,15 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
this.handleViewport(client, viewport.toObject())
|
this.handleViewport(client, viewport.toObject())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setEmoteeventmessage(emoteMessage);
|
||||||
|
|
||||||
|
emitInBatch(listener, subMessage);
|
||||||
|
}
|
||||||
|
|
||||||
// Useless now, will be useful again if we allow editing details in game
|
// Useless now, will be useful again if we allow editing details in game
|
||||||
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
@ -321,6 +332,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
debug('Leaving room %s.', socket.roomId);
|
debug('Leaving room %s.', socket.roomId);
|
||||||
|
|
||||||
room.leave(socket);
|
room.leave(socket);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(socket.roomId);
|
this.rooms.delete(socket.roomId);
|
||||||
@ -578,6 +590,13 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
this.updateRoomWithAdminData(room);
|
this.updateRoomWithAdminData(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmotePromptMessage(client: ExSocketInterface, emoteEventmessage: EmotePromptMessage) {
|
||||||
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
|
pusherToBackMessage.setEmotepromptmessage(emoteEventmessage);
|
||||||
|
|
||||||
|
client.backConnection.write(pusherToBackMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -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"
|
||||||
|
@ -811,9 +811,9 @@ has@^1.0.3:
|
|||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
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"
|
||||||
|