Merge pull request #1329 from thecodingmachine/develop

Add feature Release 1.4.11
This commit is contained in:
grégoire parant 2021-08-03 00:47:09 +02:00 committed by GitHub
commit d568fd596b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 506 additions and 8799 deletions

View file

@ -5,8 +5,9 @@
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file. - Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
- Rewrote the way authentification works: the auth jwt token can now contains an email instead of an uuid - Rewrote the way authentification works: the auth jwt token can now contains an email instead of an uuid
- Added an OpenId login flow than can be plugged to any OIDC provider. - Added an OpenId login flow than can be plugged to any OIDC provider.
- You can send a message to all rooms of your world from the console global message (user with tag admin only).
## Version 1.4.10 ## Version 1.4.11
### Updates ### Updates

View file

@ -272,7 +272,7 @@ const roomManager: IRoomManagerServer = {
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void { sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
// FIXME: we could improve return message by returning a Success|ErrorMessage message // FIXME: we could improve return message by returning a Success|ErrorMessage message
socketManager socketManager
.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage()) .sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage(), call.request.getType())
.catch((e) => console.error(e)); .catch((e) => console.error(e));
callback(null, new EmptyMessage()); callback(null, new EmptyMessage());
}, },

View file

@ -761,7 +761,7 @@ export class SocketManager {
} }
} }
async sendAdminRoomMessage(roomId: string, message: string) { async sendAdminRoomMessage(roomId: string, message: string, type: string) {
const room = await this.roomsPromises.get(roomId); const room = await this.roomsPromises.get(roomId);
if (!room) { if (!room) {
//todo: this should cause the http call to return a 500 //todo: this should cause the http call to return a 500
@ -776,7 +776,7 @@ export class SocketManager {
room.getUsers().forEach((recipient) => { room.getUsers().forEach((recipient) => {
const sendUserMessage = new SendUserMessage(); const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(message); sendUserMessage.setMessage(message);
sendUserMessage.setType("message"); sendUserMessage.setType(type);
const clientMessage = new ServerToClientMessage(); const clientMessage = new ServerToClientMessage();
clientMessage.setSendusermessage(sendUserMessage); clientMessage.setSendusermessage(sendUserMessage);

View file

@ -1,58 +1,62 @@
import "jasmine"; import "jasmine";
import {ConnectCallback, DisconnectCallback, GameRoom} from "../src/Model/GameRoom"; import { ConnectCallback, DisconnectCallback, GameRoom } from "../src/Model/GameRoom";
import {Point} from "../src/Model/Websocket/MessageUserPosition"; import { Point } from "../src/Model/Websocket/MessageUserPosition";
import {Group} from "../src/Model/Group"; 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"; import { EmoteCallback } from "_Model/Zone";
function createMockUser(userId: number): User { function createMockUser(userId: number): User {
return { return {
userId userId,
} as unknown as User; } as unknown as User;
} }
function createMockUserSocket(): UserSocket { function createMockUserSocket(): UserSocket {
return { return {} as unknown as UserSocket;
} as unknown as UserSocket;
} }
function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMessage function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMessage {
{
const positionMessage = new PositionMessage(); const positionMessage = new PositionMessage();
positionMessage.setX(x); positionMessage.setX(x);
positionMessage.setY(y); positionMessage.setY(y);
positionMessage.setDirection(Direction.DOWN); positionMessage.setDirection(Direction.DOWN);
positionMessage.setMoving(false); positionMessage.setMoving(false);
const joinRoomMessage = new JoinRoomMessage(); const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid('1'); joinRoomMessage.setUseruuid("1");
joinRoomMessage.setIpaddress('10.0.0.2'); joinRoomMessage.setIpaddress("10.0.0.2");
joinRoomMessage.setName('foo'); joinRoomMessage.setName("foo");
joinRoomMessage.setRoomid('_/global/test.json'); joinRoomMessage.setRoomid("_/global/test.json");
joinRoomMessage.setPositionmessage(positionMessage); joinRoomMessage.setPositionmessage(positionMessage);
return joinRoomMessage; return joinRoomMessage;
} }
const emote: EmoteCallback = (emoteEventMessage, listener): void => {} const emote: EmoteCallback = (emoteEventMessage, listener): void => {};
describe("GameRoom", () => { describe("GameRoom", () => {
it("should connect user1 and user2", async () => { it("should connect user1 and user2", async () => {
let connectCalledNumber: number = 0; let connectCalledNumber: number = 0;
const connect: ConnectCallback = (user: User, group: Group): void => { const connect: ConnectCallback = (user: User, group: Group): void => {
connectCalledNumber++; connectCalledNumber++;
} };
const disconnect: DisconnectCallback = (user: User, group: Group): void => { const disconnect: DisconnectCallback = (user: User, group: Group): void => {};
} const world = await GameRoom.create(
"https://play.workadventu.re/_/global/localhost/test.json",
connect,
disconnect,
160,
160,
() => {},
() => {},
() => {},
emote
);
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
const world = await GameRoom.create('https://play.workadventu.re/_/global/localhost/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote); const user2 = world.join(createMockUserSocket(), createJoinRoomMessage("2", 500, 100));
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
const user2 = world.join(createMockUserSocket(), createJoinRoomMessage('2', 500, 100));
world.updatePosition(user2, new Point(261, 100)); world.updatePosition(user2, new Point(261, 100));
@ -70,22 +74,30 @@ describe("GameRoom", () => {
let connectCalled: boolean = false; let connectCalled: boolean = false;
const connect: ConnectCallback = (user: User, group: Group): void => { const connect: ConnectCallback = (user: User, group: Group): void => {
connectCalled = true; connectCalled = true;
} };
const disconnect: DisconnectCallback = (user: User, group: Group): void => { const disconnect: DisconnectCallback = (user: User, group: Group): void => {};
} const world = await GameRoom.create(
"https://play.workadventu.re/_/global/localhost/test.json",
connect,
disconnect,
160,
160,
() => {},
() => {},
() => {},
emote
);
const world = await GameRoom.create('https://play.workadventu.re/_/global/localhost/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)); const user2 = world.join(createMockUserSocket(), createJoinRoomMessage("2", 200, 100));
const user2 = world.join(createMockUserSocket(), createJoinRoomMessage('2', 200, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
connectCalled = false; connectCalled = false;
// baz joins at the outer limit of the group // baz joins at the outer limit of the group
const user3 = world.join(createMockUserSocket(), createJoinRoomMessage('2', 311, 100)); const user3 = world.join(createMockUserSocket(), createJoinRoomMessage("2", 311, 100));
expect(connectCalled).toBe(false); expect(connectCalled).toBe(false);
@ -99,26 +111,35 @@ describe("GameRoom", () => {
let disconnectCallNumber: number = 0; let disconnectCallNumber: number = 0;
const connect: ConnectCallback = (user: User, group: Group): void => { const connect: ConnectCallback = (user: User, group: Group): void => {
connectCalled = true; connectCalled = true;
} };
const disconnect: DisconnectCallback = (user: User, group: Group): void => { const disconnect: DisconnectCallback = (user: User, group: Group): void => {
disconnectCallNumber++; disconnectCallNumber++;
} };
const world = await GameRoom.create('https://play.workadventu.re/_/global/localhost/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote); const world = await GameRoom.create(
"https://play.workadventu.re/_/global/localhost/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));
const user2 = world.join(createMockUserSocket(), createJoinRoomMessage('2', 259, 100)); const user2 = world.join(createMockUserSocket(), createJoinRoomMessage("2", 259, 100));
expect(connectCalled).toBe(true); expect(connectCalled).toBe(true);
expect(disconnectCallNumber).toBe(0); expect(disconnectCallNumber).toBe(0);
world.updatePosition(user2, new Point(100+160+160+1, 100)); world.updatePosition(user2, new Point(100 + 160 + 160 + 1, 100));
expect(disconnectCallNumber).toBe(2); expect(disconnectCallNumber).toBe(2);
world.updatePosition(user2, new Point(262, 100)); world.updatePosition(user2, new Point(262, 100));
expect(disconnectCallNumber).toBe(2); expect(disconnectCallNumber).toBe(2);
}); });
});
})

8393
front/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -51,6 +51,7 @@
"phaser3-rex-plugins": "^1.1.42", "phaser3-rex-plugins": "^1.1.42",
"queue-typescript": "^1.0.1", "queue-typescript": "^1.0.1",
"quill": "1.3.6", "quill": "1.3.6",
"quill-delta-to-html": "^0.12.0",
"rxjs": "^6.6.3", "rxjs": "^6.6.3",
"simple-peer": "^9.11.0", "simple-peer": "^9.11.0",
"socket.io-client": "^2.3.0", "socket.io-client": "^2.3.0",

View file

@ -1,92 +0,0 @@
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
import type {RoomConnection} from "../Connexion/RoomConnection";
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
import {soundManager} from "../Phaser/Game/SoundManager";
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
export class GlobalMessageManager {
constructor(private Connection: RoomConnection) {
this.initialise();
}
initialise(){
//receive signal to show message
this.Connection.receivePlayGlobalMessage((message: PlayGlobalMessageInterface) => {
this.playMessage(message);
});
//receive signal to close message
this.Connection.receiveStopGlobalMessage((messageId: string) => {
this.stopMessage(messageId);
});
//receive signal to close message
this.Connection.receiveTeleportMessage((map: string) => {
console.log('map to teleport user', map);
//TODO teleport user on map
});
}
private playMessage(message : PlayGlobalMessageInterface){
const previousMessage = document.getElementById(this.getHtmlMessageId(message.id));
if(previousMessage){
previousMessage.remove();
}
if(AdminMessageEventTypes.audio === message.type){
this.playAudioMessage(message.id, message.message);
}
if(AdminMessageEventTypes.admin === message.type){
this.playTextMessage(message.id, message.message);
}
}
private playAudioMessage(messageId : string, urlMessage: string) {
soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
}
private playTextMessage(messageId : string, htmlMessage: string){
//add button to clear message
const buttonText = document.createElement('p');
buttonText.id = 'button-clear-message';
buttonText.innerText = 'Clear';
const buttonMainConsole = document.createElement('div');
buttonMainConsole.classList.add('clear');
buttonMainConsole.appendChild(buttonText);
buttonMainConsole.addEventListener('click', () => {
messageContainer.style.top = '-80%';
setTimeout(() => {
messageContainer.remove();
buttonMainConsole.remove();
});
});
//create content message
const messageCotent = document.createElement('div');
messageCotent.innerHTML = htmlMessage;
messageCotent.className = "content-message";
//add message container
const messageContainer = document.createElement('div');
messageContainer.id = this.getHtmlMessageId(messageId);
messageContainer.className = "message-container";
messageContainer.appendChild(messageCotent);
messageContainer.appendChild(buttonMainConsole);
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
mainSectionDiv.appendChild(messageContainer);
}
private stopMessage(messageId: string){
HtmlUtils.removeElementByIdOrFail<HTMLDivElement>(this.getHtmlMessageId(messageId));
}
private getHtmlMessageId(messageId: string) : string{
return `message-${messageId}`;
}
}

View file

@ -1,95 +0,0 @@
import type {TypeMessageInterface} from "./UserMessageManager";
import {HtmlUtils} from "../WebRtc/HtmlUtils";
let modalTimeOut : NodeJS.Timeout;
export class TypeMessageExt implements TypeMessageInterface{
private nbSecond = 0;
private maxNbSecond = 10;
private titleMessage = 'IMPORTANT !';
showMessage(message: string, canDeleteMessage: boolean = true): void {
//delete previous modal
try{
if(modalTimeOut){
clearTimeout(modalTimeOut);
}
const modal = HtmlUtils.getElementByIdOrFail('report-message-user');
modal.remove();
}catch (err){
console.error(err);
}
//create new modal
const div : HTMLDivElement = document.createElement('div');
div.classList.add('modal-report-user');
div.id = 'report-message-user';
div.style.backgroundColor = '#000000e0';
const img : HTMLImageElement = document.createElement('img');
img.src = 'resources/logos/report.svg';
div.appendChild(img);
const title : HTMLParagraphElement = document.createElement('p');
title.id = 'title-report-user';
title.innerText = `${this.titleMessage} (${this.maxNbSecond})`;
div.appendChild(title);
const p : HTMLParagraphElement = document.createElement('p');
p.id = 'body-report-user'
p.innerText = message;
div.appendChild(p);
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
mainSectionDiv.appendChild(div);
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
// FIXME: this will fail on iOS
// We should move the sound playing into the GameScene and listen to the event of a report using a store
try {
reportMessageAudio.play();
} catch (e) {
console.error(e);
}
this.nbSecond = this.maxNbSecond;
setTimeout((c) => {
this.forMessage(title, canDeleteMessage);
}, 1000);
}
forMessage(title: HTMLParagraphElement, canDeleteMessage: boolean = true){
this.nbSecond -= 1;
title.innerText = `${this.titleMessage} (${this.nbSecond})`;
if(this.nbSecond > 0){
modalTimeOut = setTimeout(() => {
this.forMessage(title, canDeleteMessage);
}, 1000);
}else {
title.innerText = this.titleMessage;
if (!canDeleteMessage) {
return;
}
const imgCancel: HTMLImageElement = document.createElement('img');
imgCancel.id = 'cancel-report-user';
imgCancel.src = 'resources/logos/close.svg';
const div = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('report-message-user');
div.appendChild(imgCancel);
imgCancel.addEventListener('click', () => {
div.remove();
});
}
}
}
export class Message extends TypeMessageExt {}
export class Ban extends TypeMessageExt {}
export class Banned extends TypeMessageExt {
showMessage(message: string){
super.showMessage(message, false);
}
}

View file

@ -1,43 +1,34 @@
import * as TypeMessages from "./TypeMessage"; import { AdminMessageEventTypes, adminMessagesService } from "../Connexion/AdminMessagesService";
import {Banned} from "./TypeMessage"; import { textMessageContentStore, textMessageVisibleStore } from "../Stores/TypeMessageStore/TextMessageStore";
import {adminMessagesService} from "../Connexion/AdminMessagesService"; import { soundPlayingStore } from "../Stores/SoundPlayingStore";
import { UPLOADER_URL } from "../Enum/EnvironmentVariable";
export interface TypeMessageInterface { import { banMessageContentStore, banMessageVisibleStore } from "../Stores/TypeMessageStore/BanMessageStore";
showMessage(message: string): void;
}
class UserMessageManager { class UserMessageManager {
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
receiveBannedMessageListener!: Function; receiveBannedMessageListener!: Function;
constructor() { constructor() {
const valueTypeMessageTab = Object.values(TypeMessages);
Object.keys(TypeMessages).forEach((value: string, index: number) => {
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
});
adminMessagesService.messageStream.subscribe((event) => { adminMessagesService.messageStream.subscribe((event) => {
const typeMessage = this.showMessage(event.type, event.text); textMessageVisibleStore.set(false);
if(typeMessage instanceof Banned) { banMessageVisibleStore.set(false);
if (event.type === AdminMessageEventTypes.admin) {
textMessageContentStore.set(event.text);
textMessageVisibleStore.set(true);
} else if (event.type === AdminMessageEventTypes.audio) {
soundPlayingStore.playSound(UPLOADER_URL + event.text);
} else if (event.type === AdminMessageEventTypes.ban) {
banMessageContentStore.set(event.text);
banMessageVisibleStore.set(true);
} else if (event.type === AdminMessageEventTypes.banned) {
banMessageContentStore.set(event.text);
banMessageVisibleStore.set(true);
this.receiveBannedMessageListener(); this.receiveBannedMessageListener();
} }
}) });
} }
showMessage(type: string, message: string) { setReceiveBanListener(callback: Function) {
const classTypeMessage = this.typeMessages.get(type.toLowerCase());
if (!classTypeMessage) {
console.error('Message unknown');
return;
}
classTypeMessage.showMessage(message);
return classTypeMessage;
}
setReceiveBanListener(callback: Function){
this.receiveBannedMessageListener = callback; this.receiveBannedMessageListener = callback;
} }
} }
export const userMessageManager = new UserMessageManager() export const userMessageManager = new UserMessageManager();

View file

@ -1,18 +1,20 @@
import * as tg from "generic-type-guard"; import * as tg from "generic-type-guard";
import {isMenuItemRegisterEvent} from "./ui/MenuItemRegisterEvent"; import { isMenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
export const isSetVariableEvent = export const isSetVariableEvent = new tg.IsInterface()
new tg.IsInterface().withProperties({ .withProperties({
key: tg.isString, key: tg.isString,
value: tg.isUnknown, value: tg.isUnknown,
}).get(); })
.get();
/** /**
* A message sent from the iFrame to the game to change the value of the property of the layer * A message sent from the iFrame to the game to change the value of the property of the layer
*/ */
export type SetVariableEvent = tg.GuardedType<typeof isSetVariableEvent>; export type SetVariableEvent = tg.GuardedType<typeof isSetVariableEvent>;
export const isSetVariableIframeEvent = export const isSetVariableIframeEvent = new tg.IsInterface()
new tg.IsInterface().withProperties({ .withProperties({
type: tg.isSingletonString("setVariable"), type: tg.isSingletonString("setVariable"),
data: isSetVariableEvent data: isSetVariableEvent,
}).get(); })
.get();

View file

@ -27,6 +27,10 @@
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility"; import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore"; import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte"; import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
import AdminMessage from "./TypeMessage/BanMessage.svelte";
import TextMessage from "./TypeMessage/TextMessage.svelte";
import {banMessageVisibleStore} from "../Stores/TypeMessageStore/BanMessageStore";
import {textMessageVisibleStore} from "../Stores/TypeMessageStore/TextMessageStore";
import {warningContainerStore} from "../Stores/MenuStore"; import {warningContainerStore} from "../Stores/MenuStore";
import WarningContainer from "./WarningContainer/WarningContainer.svelte"; import WarningContainer from "./WarningContainer/WarningContainer.svelte";
@ -60,6 +64,16 @@
<EnableCameraScene game={game}></EnableCameraScene> <EnableCameraScene game={game}></EnableCameraScene>
</div> </div>
{/if} {/if}
{#if $banMessageVisibleStore}
<div>
<AdminMessage></AdminMessage>
</div>
{/if}
{#if $textMessageVisibleStore}
<div>
<TextMessage></TextMessage>
</div>
{/if}
{#if $soundPlayingStore} {#if $soundPlayingStore}
<div> <div>
<AudioPlaying url={$soundPlayingStore} /> <AudioPlaying url={$soundPlayingStore} />

View file

@ -9,8 +9,8 @@
export let game: Game; export let game: Game;
let inputSendTextActive = true; let inputSendTextActive = true;
let uploadMusicActive = false; let uploadMusicActive = false;
let handleSendText: { sendTextMessage(): void }; let handleSendText: { sendTextMessage(broadcast: boolean): void };
let handleSendAudio: { sendAudioMessage(): Promise<void> }; let handleSendAudio: { sendAudioMessage(broadcast: boolean): Promise<void> };
let broadcastToWorld = false; let broadcastToWorld = false;
function closeConsoleGlobalMessage() { function closeConsoleGlobalMessage() {
@ -35,10 +35,10 @@
function send() { function send() {
if (inputSendTextActive) { if (inputSendTextActive) {
handleSendText.sendTextMessage(); handleSendText.sendTextMessage(broadcastToWorld);
} }
if (uploadMusicActive) { if (uploadMusicActive) {
handleSendAudio.sendAudioMessage(); handleSendAudio.sendAudioMessage(broadcastToWorld);
} }
} }
</script> </script>

View file

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore"; import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
import { onMount } from "svelte"; import {onDestroy, onMount} from "svelte";
import type { Game } from "../../Phaser/Game/Game"; import type { Game } from "../../Phaser/Game/Game";
import type { GameManager } from "../../Phaser/Game/GameManager"; import type { GameManager } from "../../Phaser/Game/GameManager";
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService"; import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
import type { Quill } from "quill"; import type { Quill } from "quill";
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
//toolbar //toolbar
const toolbarOptions = [ const toolbarOptions = [
@ -34,27 +34,27 @@
export let game: Game; export let game: Game;
export let gameManager: GameManager; export let gameManager: GameManager;
let gameScene = gameManager.getCurrentGameScene(game.findAnyScene()); const gameScene = gameManager.getCurrentGameScene(game.findAnyScene());
let quill: Quill; let quill: Quill;
let INPUT_CONSOLE_MESSAGE: HTMLDivElement; let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
const MESSAGE_TYPE = AdminMessageEventTypes.admin; const MESSAGE_TYPE = AdminMessageEventTypes.admin;
export const handleSending = { export const handleSending = {
sendTextMessage() { sendTextMessage(broadcastToWorld: boolean) {
if (gameScene == undefined) { if (gameScene == undefined) {
return; return;
} }
const text = quill.getText(0, quill.getLength()); const text = JSON.stringify(quill.getContents(0, quill.getLength()));
const GlobalMessage: PlayGlobalMessageInterface = { const textGlobalMessage: PlayGlobalMessageInterface = {
id: "1", // FIXME: use another ID? type: MESSAGE_TYPE,
message: text, content: text,
type: MESSAGE_TYPE broadcastToWorld: broadcastToWorld
}; };
quill.deleteText(0, quill.getLength()); quill.deleteText(0, quill.getLength());
gameScene.connection?.emitGlobalMessage(GlobalMessage); gameScene.connection?.emitGlobalMessage(textGlobalMessage);
disableConsole(); disableConsole();
} }
} }
@ -73,14 +73,13 @@
}, },
}); });
quill.on('selection-change', function (range, oldRange) { consoleGlobalMessageManagerFocusStore.set(true);
if (range === null && oldRange !== null) {
consoleGlobalMessageManagerFocusStore.set(false);
} else if (range !== null && oldRange === null)
consoleGlobalMessageManagerFocusStore.set(true);
});
}); });
onDestroy(() => {
consoleGlobalMessageManagerFocusStore.set(false);
})
function disableConsole() { function disableConsole() {
consoleGlobalMessageManagerVisibleStore.set(false); consoleGlobalMessageManagerVisibleStore.set(false);
consoleGlobalMessageManagerFocusStore.set(false); consoleGlobalMessageManagerFocusStore.set(false);

View file

@ -4,8 +4,8 @@
import type { GameManager } from "../../Phaser/Game/GameManager"; import type { GameManager } from "../../Phaser/Game/GameManager";
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore"; import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService"; import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
import uploadFile from "../images/music-file.svg"; import uploadFile from "../images/music-file.svg";
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
interface EventTargetFiles extends EventTarget { interface EventTargetFiles extends EventTarget {
files: Array<File>; files: Array<File>;
@ -23,7 +23,7 @@
const AUDIO_TYPE = AdminMessageEventTypes.audio; const AUDIO_TYPE = AdminMessageEventTypes.audio;
export const handleSending = { export const handleSending = {
async sendAudioMessage() { async sendAudioMessage(broadcast: boolean) {
if (gameScene == undefined) { if (gameScene == undefined) {
return; return;
} }
@ -38,13 +38,13 @@
fd.append('file', selectedFile); fd.append('file', selectedFile);
const res = await gameScene.connection?.uploadAudio(fd); const res = await gameScene.connection?.uploadAudio(fd);
const GlobalMessage: PlayGlobalMessageInterface = { const audioGlobalMessage: PlayGlobalMessageInterface = {
id: (res as { id: string }).id, content: (res as { path: string }).path,
message: (res as { path: string }).path, type: AUDIO_TYPE,
type: AUDIO_TYPE broadcastToWorld: broadcast
} }
inputAudio.value = ''; inputAudio.value = '';
gameScene.connection?.emitGlobalMessage(GlobalMessage); gameScene.connection?.emitGlobalMessage(audioGlobalMessage);
disableConsole(); disableConsole();
} }
} }

View file

@ -0,0 +1,96 @@
<script lang="ts">
import { fly } from "svelte/transition";
import {banMessageVisibleStore, banMessageContentStore} from "../../Stores/TypeMessageStore/BanMessageStore";
import {onMount} from "svelte";
const text = $banMessageContentStore;
const NAME_BUTTON = 'Ok';
let nbSeconds = 10;
let nameButton = '';
onMount(() => {
timeToRead()
})
function timeToRead() {
nbSeconds -= 1;
nameButton = nbSeconds.toString();
if ( nbSeconds > 0 ) {
setTimeout( () => {
timeToRead();
}, 1000);
} else {
nameButton = NAME_BUTTON;
}
}
function closeBanMessage() {
banMessageVisibleStore.set(false);
}
</script>
<div class="main-ban-message nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
<h2 class="title-ban-message"><img src="resources/logos/report.svg" alt="***"/> Important message <img src="resources/logos/report.svg" alt="***"/></h2>
<div class="content-ban-message">
<p>{text}</p>
</div>
<div class="footer-ban-message">
<button type="button" class="nes-btn {nameButton === NAME_BUTTON ? 'is-primary' : 'is-error'}" disabled="{!(nameButton === NAME_BUTTON)}" on:click|preventDefault={closeBanMessage}>{nameButton}</button>
</div>
<audio id="report-message" autoplay>
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
</audio>
</div>
<style lang="scss">
div.main-ban-message {
display: flex;
flex-direction: column;
position: relative;
top: 15vh;
height: 70vh;
width: 60vw;
margin-left: auto;
margin-right: auto;
padding-bottom: 0;
pointer-events: auto;
background-color: #333333;
color: whitesmoke;
h2.title-ban-message {
flex: 1 1 auto;
max-height: 50px;
margin-bottom: 20px;
text-align: center;
img {
height: 50px;
}
}
div.content-ban-message {
flex: 1 1 auto;
max-height: calc(100% - 50px);
overflow: auto;
p {
white-space: pre-wrap;
}
}
div.footer-ban-message {
height: 50px;
margin-top: 10px;
text-align: center;
button {
width: 88px;
height: 44px;
}
}
}
</style>

View file

@ -0,0 +1,59 @@
<script lang="ts">
import { fly } from "svelte/transition";
import {textMessageContentStore, textMessageVisibleStore} from "../../Stores/TypeMessageStore/TextMessageStore";
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
const content = JSON.parse($textMessageContentStore);
const converter = new QuillDeltaToHtmlConverter(content.ops, {inlineStyles: true});
const NAME_BUTTON = 'Ok';
function closeTextMessage() {
textMessageVisibleStore.set(false);
}
function onKeyDown(e:KeyboardEvent) {
if (e.key === 'Escape') {
closeTextMessage();
}
}
</script>
<svelte:window on:keydown={onKeyDown}/>
<div class="main-text-message nes-container is-rounded" transition:fly="{{ x: -1000, duration: 500 }}">
<div class="content-text-message">
{@html converter.convert()}
</div>
<div class="footer-text-message">
<button type="button" class="nes-btn is-primary" on:click|preventDefault={closeTextMessage}>{NAME_BUTTON}</button>
</div>
</div>
<style lang="scss">
div.main-text-message {
display: flex;
flex-direction: column;
max-height: 25vh;
width: 80vw;
margin-right: auto;
margin-left: auto;
padding-bottom: 0;
pointer-events: auto;
background-color: #333333;
div.content-text-message {
flex: 1 1 auto;
max-height: calc(100% - 50px);
color: whitesmoke;
overflow: auto;
}
div.footer-text-message {
height: 50px;
text-align: center;
}
}
</style>

View file

@ -110,9 +110,9 @@ export interface RoomJoinedMessageInterface {
} }
export interface PlayGlobalMessageInterface { export interface PlayGlobalMessageInterface {
id: string;
type: string; type: string;
message: string; content: string;
broadcastToWorld: boolean;
} }
export interface OnConnectInterface { export interface OnConnectInterface {

View file

@ -597,7 +597,7 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) { /* public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) {
return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => { return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => {
callback({ callback({
id: message.getId(), id: message.getId(),
@ -605,7 +605,7 @@ export class RoomConnection implements RoomConnection {
message: message.getMessage(), message: message.getMessage(),
}); });
}); });
} }*/
public receiveStopGlobalMessage(callback: (messageId: string) => void) { public receiveStopGlobalMessage(callback: (messageId: string) => void) {
return this.onMessage(EventMessage.STOP_GLOBAL_MESSAGE, (message: StopGlobalMessage) => { return this.onMessage(EventMessage.STOP_GLOBAL_MESSAGE, (message: StopGlobalMessage) => {
@ -619,11 +619,11 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public emitGlobalMessage(message: PlayGlobalMessageInterface) { public emitGlobalMessage(message: PlayGlobalMessageInterface): void {
const playGlobalMessage = new PlayGlobalMessage(); const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id);
playGlobalMessage.setType(message.type); playGlobalMessage.setType(message.type);
playGlobalMessage.setMessage(message.message); playGlobalMessage.setContent(message.content);
playGlobalMessage.setBroadcasttoworld(message.broadcastToWorld);
const clientToServerMessage = new ClientToServerMessage(); const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setPlayglobalmessage(playGlobalMessage); clientToServerMessage.setPlayglobalmessage(playGlobalMessage);

View file

@ -1,5 +1,4 @@
import type { Subscription } from "rxjs"; import type { Subscription } from "rxjs";
import { GlobalMessageManager } from "../../Administration/GlobalMessageManager";
import { userMessageManager } from "../../Administration/UserMessageManager"; import { userMessageManager } from "../../Administration/UserMessageManager";
import { iframeListener } from "../../Api/IframeListener"; import { iframeListener } from "../../Api/IframeListener";
import { connectionManager } from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
@ -155,7 +154,6 @@ export class GameScene extends DirtyScene {
private playersPositionInterpolator = new PlayersPositionInterpolator(); private playersPositionInterpolator = new PlayersPositionInterpolator();
public connection: RoomConnection | undefined; public connection: RoomConnection | undefined;
private simplePeer!: SimplePeer; private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>; private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: ( private connectionAnswerPromiseResolve!: (
value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface> value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>
@ -694,7 +692,6 @@ export class GameScene extends DirtyScene {
peerStore.connectToSimplePeer(this.simplePeer); peerStore.connectToSimplePeer(this.simplePeer);
screenSharingPeerStore.connectToSimplePeer(this.simplePeer); screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
videoFocusStore.connectToSimplePeer(this.simplePeer); videoFocusStore.connectToSimplePeer(this.simplePeer);
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
const self = this; const self = this;

View file

@ -1,12 +1,12 @@
import type { Direction } from "../../types"; import type { Direction } from "../../types";
import type {GameScene} from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import { touchScreenManager } from "../../Touch/TouchScreenManager";
import {MobileJoystick} from "../Components/MobileJoystick"; import { MobileJoystick } from "../Components/MobileJoystick";
import {enableUserInputsStore} from "../../Stores/UserInputStore"; import { enableUserInputsStore } from "../../Stores/UserInputStore";
interface UserInputManagerDatum { interface UserInputManagerDatum {
keyInstance: Phaser.Input.Keyboard.Key; keyInstance: Phaser.Input.Keyboard.Key;
event: UserInputEvent event: UserInputEvent;
} }
export enum UserInputEvent { export enum UserInputEvent {
@ -20,10 +20,9 @@ export enum UserInputEvent {
JoystickMove, JoystickMove,
} }
//we cannot use a map structure so we have to create a replacement //we cannot use a map structure so we have to create a replacement
export class ActiveEventList { export class ActiveEventList {
private eventMap : Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>(); private eventMap: Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>();
get(event: UserInputEvent): boolean { get(event: UserInputEvent): boolean {
return this.eventMap.get(event) || false; return this.eventMap.get(event) || false;
@ -43,7 +42,7 @@ export class ActiveEventList {
export class UserInputManager { export class UserInputManager {
private KeysCode!: UserInputManagerDatum[]; private KeysCode!: UserInputManagerDatum[];
private Scene: GameScene; private Scene: GameScene;
private isInputDisabled : boolean; private isInputDisabled: boolean;
private joystick!: MobileJoystick; private joystick!: MobileJoystick;
private joystickEvents = new ActiveEventList(); private joystickEvents = new ActiveEventList();
@ -61,8 +60,8 @@ export class UserInputManager {
} }
enableUserInputsStore.subscribe((enable) => { enableUserInputsStore.subscribe((enable) => {
enable ? this.restoreControls() : this.disableControls() enable ? this.restoreControls() : this.disableControls();
}) });
} }
initVirtualJoystick() { initVirtualJoystick() {
@ -91,39 +90,81 @@ export class UserInputManager {
}); });
} }
initKeyBoardEvent(){ initKeyBoardEvent() {
this.KeysCode = [ this.KeysCode = [
{event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z, false) }, {
{event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W, false) }, event: UserInputEvent.MoveUp,
{event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q, false) }, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z, false),
{event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A, false) }, },
{event: UserInputEvent.MoveDown, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S, false) }, {
{event: UserInputEvent.MoveRight, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D, false) }, event: UserInputEvent.MoveUp,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W, false),
},
{
event: UserInputEvent.MoveLeft,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q, false),
},
{
event: UserInputEvent.MoveLeft,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A, false),
},
{
event: UserInputEvent.MoveDown,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S, false),
},
{
event: UserInputEvent.MoveRight,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D, false),
},
{event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP, false) }, {
{event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT, false) }, event: UserInputEvent.MoveUp,
{event: UserInputEvent.MoveDown, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN, false) }, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP, false),
{event: UserInputEvent.MoveRight, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT, false) }, },
{
event: UserInputEvent.MoveLeft,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT, false),
},
{
event: UserInputEvent.MoveDown,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN, false),
},
{
event: UserInputEvent.MoveRight,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT, false),
},
{event: UserInputEvent.SpeedUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT, false) }, {
event: UserInputEvent.SpeedUp,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT, false),
},
{event: UserInputEvent.Interact, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E, false) }, {
{event: UserInputEvent.Interact, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false) }, event: UserInputEvent.Interact,
{event: UserInputEvent.Shout, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false) }, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E, false),
},
{
event: UserInputEvent.Interact,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false),
},
{
event: UserInputEvent.Shout,
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false),
},
]; ];
} }
clearAllListeners(){ clearAllListeners() {
this.Scene.input.keyboard.removeAllListeners(); this.Scene.input.keyboard.removeAllListeners();
} }
//todo: should we also disable the joystick? //todo: should we also disable the joystick?
disableControls(){ disableControls() {
this.Scene.input.keyboard.removeAllKeys(); this.Scene.input.keyboard.removeAllKeys();
this.isInputDisabled = true; this.isInputDisabled = true;
} }
restoreControls(){ restoreControls() {
this.initKeyBoardEvent(); this.initKeyBoardEvent();
this.isInputDisabled = false; this.isInputDisabled = false;
} }
@ -135,27 +176,27 @@ export class UserInputManager {
this.joystickEvents.forEach((value, key) => { this.joystickEvents.forEach((value, key) => {
if (value) { if (value) {
switch (key) { switch (key) {
case UserInputEvent.MoveUp: case UserInputEvent.MoveUp:
case UserInputEvent.MoveDown: case UserInputEvent.MoveDown:
this.joystickForceAccuY += this.joystick.forceY; this.joystickForceAccuY += this.joystick.forceY;
if (Math.abs(this.joystickForceAccuY) > this.joystickForceThreshold) { if (Math.abs(this.joystickForceAccuY) > this.joystickForceThreshold) {
eventsMap.set(key, value); eventsMap.set(key, value);
this.joystickForceAccuY = 0; this.joystickForceAccuY = 0;
} }
break; break;
case UserInputEvent.MoveLeft: case UserInputEvent.MoveLeft:
case UserInputEvent.MoveRight: case UserInputEvent.MoveRight:
this.joystickForceAccuX += this.joystick.forceX; this.joystickForceAccuX += this.joystick.forceX;
if (Math.abs(this.joystickForceAccuX) > this.joystickForceThreshold) { if (Math.abs(this.joystickForceAccuX) > this.joystickForceThreshold) {
eventsMap.set(key, value); eventsMap.set(key, value);
this.joystickForceAccuX = 0; this.joystickForceAccuX = 0;
} }
break; break;
} }
} }
}); });
eventsMap.set(UserInputEvent.JoystickMove, this.joystickEvents.any()); eventsMap.set(UserInputEvent.JoystickMove, this.joystickEvents.any());
this.KeysCode.forEach(d => { this.KeysCode.forEach((d) => {
if (d.keyInstance.isDown) { if (d.keyInstance.isDown) {
eventsMap.set(d.event, true); eventsMap.set(d.event, true);
} }
@ -163,18 +204,18 @@ export class UserInputManager {
return eventsMap; return eventsMap;
} }
spaceEvent(callback : Function){ spaceEvent(callback: Function) {
this.Scene.input.keyboard.on('keyup-SPACE', (event: Event) => { this.Scene.input.keyboard.on("keyup-SPACE", (event: Event) => {
callback(); callback();
return event; return event;
}); });
} }
addSpaceEventListner(callback : Function){ addSpaceEventListner(callback: Function) {
this.Scene.input.keyboard.addListener('keyup-SPACE', callback); this.Scene.input.keyboard.addListener("keyup-SPACE", callback);
} }
removeSpaceEventListner(callback : Function){ removeSpaceEventListner(callback: Function) {
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback); this.Scene.input.keyboard.removeListener("keyup-SPACE", callback);
} }
destroy(): void { destroy(): void {
@ -182,8 +223,11 @@ export class UserInputManager {
} }
private initMouseWheel() { private initMouseWheel() {
this.Scene.input.on('wheel', (pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => { this.Scene.input.on(
this.Scene.zoomByFactor(1 - deltaY / 53 * 0.1); "wheel",
}); (pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => {
this.Scene.zoomByFactor(1 - (deltaY / 53) * 0.1);
}
);
} }
} }

View file

@ -0,0 +1,5 @@
import { writable } from "svelte/store";
export const banMessageVisibleStore = writable(false);
export const banMessageContentStore = writable("");

View file

@ -0,0 +1,5 @@
import { writable } from "svelte/store";
export const textMessageVisibleStore = writable(false);
export const textMessageContentStore = writable("");

View file

@ -9,8 +9,7 @@ section.section-input-send-text {
} }
div.input-send-text{ div.input-send-text{
height: auto; height: calc(100% - 100px);
max-height: calc(100% - 100px);
overflow: auto; overflow: auto;
color: whitesmoke; color: whitesmoke;
@ -20,5 +19,13 @@ section.section-input-send-text {
color: whitesmoke; color: whitesmoke;
font-size: 1rem; font-size: 1rem;
} }
.ql-tooltip {
top: 40% !important;
left: 20% !important;
color: whitesmoke;
background-color: #333333;
}
} }
} }

View file

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

View file

@ -3475,6 +3475,11 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.truncate@^4.4.2: lodash.truncate@^4.4.2:
version "4.4.2" version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@ -4497,6 +4502,13 @@ queue-typescript@^1.0.1:
dependencies: dependencies:
linked-list-typescript "^1.0.11" linked-list-typescript "^1.0.11"
quill-delta-to-html@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/quill-delta-to-html/-/quill-delta-to-html-0.12.0.tgz#ee572cfa208390d99d7646795358ddb81eaea17c"
integrity sha512-Yy6U2e7ov+ZlrFbj5/GbqOBCRjyNu+vuphy0Pk+7668zIMTVHJglZY2JNa++1/zkSiqptPBmP/CpsDzC4Wznsw==
dependencies:
lodash.isequal "^4.5.0"
quill-delta@^3.6.2: quill-delta@^3.6.2:
version "3.6.3" version "3.6.3"
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032" resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"

View file

@ -123,9 +123,9 @@ message VariableWithTagMessage {
} }
message PlayGlobalMessage { message PlayGlobalMessage {
string id = 1; string type = 1;
string type = 2; string content = 2;
string message = 3; bool broadcastToWorld = 3;
} }
message StopGlobalMessage { message StopGlobalMessage {
@ -445,6 +445,7 @@ message AdminMessage {
message AdminRoomMessage { message AdminRoomMessage {
string message = 1; string message = 1;
string roomId = 2; string roomId = 2;
string type = 3;
} }
// A message sent by an administrator to absolutely everybody // A message sent by an administrator to absolutely everybody

View file

@ -123,6 +123,18 @@ class AdminApi {
return data.data; return data.data;
}); });
} }
async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!"));
}
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
headers: { Authorization: `${ADMIN_API_TOKEN}` },
}).then((data) => {
return data.data;
});
}
} }
export const adminApi = new AdminApi(); export const adminApi = new AdminApi();

View file

@ -1,45 +1,45 @@
import { PusherRoom } from "../Model/PusherRoom"; import { PusherRoom } from "../Model/PusherRoom";
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface";
import { import {
AdminMessage,
AdminPusherToBackMessage,
AdminRoomMessage,
BanMessage,
CharacterLayerMessage,
EmoteEventMessage,
EmotePromptMessage,
GroupDeleteMessage, GroupDeleteMessage,
ItemEventMessage, ItemEventMessage,
JoinRoomMessage,
PlayGlobalMessage, PlayGlobalMessage,
PusherToBackMessage,
QueryJitsiJwtMessage,
RefreshRoomMessage,
ReportPlayerMessage,
RoomJoinedMessage, RoomJoinedMessage,
SendJitsiJwtMessage,
ServerToAdminClientMessage,
ServerToClientMessage, ServerToClientMessage,
SetPlayerDetailsMessage, SetPlayerDetailsMessage,
SilentMessage, SilentMessage,
SubMessage, SubMessage,
ReportPlayerMessage, UserJoinedRoomMessage,
UserLeftMessage, UserLeftMessage,
UserLeftRoomMessage,
UserMovesMessage, UserMovesMessage,
ViewportMessage, ViewportMessage,
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
JoinRoomMessage,
CharacterLayerMessage,
PusherToBackMessage,
WorldFullMessage,
WorldConnexionMessage, WorldConnexionMessage,
AdminPusherToBackMessage,
ServerToAdminClientMessage,
EmoteEventMessage,
UserJoinedRoomMessage,
UserLeftRoomMessage,
AdminMessage,
BanMessage,
TokenExpiredMessage, TokenExpiredMessage,
RefreshRoomMessage,
EmotePromptMessage,
VariableMessage, VariableMessage,
ErrorMessage, ErrorMessage,
WorldFullMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { ADMIN_API_URL, JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable"; import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { adminApi } from "./AdminApi"; import { adminApi } from "./AdminApi";
import { emitInBatch } from "./IoSocketHelpers"; import { emitInBatch } from "./IoSocketHelpers";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import { JITSI_URL } from "../Enum/EnvironmentVariable";
import { clientEventsEmitter } from "./ClientEventsEmitter"; import { clientEventsEmitter } from "./ClientEventsEmitter";
import { gaugeManager } from "./GaugeManager"; import { gaugeManager } from "./GaugeManager";
import { apiClientRepository } from "./ApiClientRepository"; import { apiClientRepository } from "./ApiClientRepository";
@ -389,17 +389,6 @@ export class SocketManager implements ZoneEventListener {
} }
} }
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
if (!client.tags.includes("admin")) {
//In case of xss injection, we just kill the connection.
throw "Client is not an admin!";
}
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
client.backConnection.write(pusherToBackMessage);
}
public getWorlds(): Map<string, PusherRoom> { public getWorlds(): Map<string, PusherRoom> {
return this.rooms; return this.rooms;
} }
@ -585,6 +574,7 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true); client.send(serverToClientMessage.serializeBinary().buffer, true);
} }
} }
public emitTokenExpiredMessage(client: WebSocket) { public emitTokenExpiredMessage(client: WebSocket) {
const errorMessage = new TokenExpiredMessage(); const errorMessage = new TokenExpiredMessage();
@ -620,6 +610,36 @@ export class SocketManager implements ZoneEventListener {
client.backConnection.write(pusherToBackMessage); client.backConnection.write(pusherToBackMessage);
} }
public async emitPlayGlobalMessage(
client: ExSocketInterface,
playGlobalMessageEvent: PlayGlobalMessage
): Promise<void> {
if (!client.tags.includes("admin")) {
throw "Client is not an admin!";
}
const clientRoomUrl = client.roomId;
let tabUrlRooms: string[];
if (playGlobalMessageEvent.getBroadcasttoworld()) {
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
} else {
tabUrlRooms = [clientRoomUrl];
}
const roomMessage = new AdminRoomMessage();
roomMessage.setMessage(playGlobalMessageEvent.getContent());
roomMessage.setType(playGlobalMessageEvent.getType());
for (const roomUrl of tabUrlRooms) {
const apiRoom = await apiClientRepository.getClient(roomUrl);
roomMessage.setRoomid(roomUrl);
apiRoom.sendAdminMessageToRoom(roomMessage, (response) => {
return;
});
}
}
} }
export const socketManager = new SocketManager(); export const socketManager = new SocketManager();