Merge branch 'develop' of github.com:thecodingmachine/workadventure into loadTileset

This commit is contained in:
GRL 2021-07-29 15:35:32 +02:00
commit 5f7361156b
21 changed files with 88 additions and 49 deletions

View File

@ -28,7 +28,7 @@
- Use `WA.state.[any variable]: unknown` to access directly any variable (this is a shortcut to using `WA.state.loadVariable` and `WA.state.saveVariable`) - Use `WA.state.[any variable]: unknown` to access directly any variable (this is a shortcut to using `WA.state.loadVariable` and `WA.state.saveVariable`)
- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked.
- The text chat was redesigned to be prettier and to use more features : - The text chat was redesigned to be prettier and to use more features :
- The chat is now persistent bewteen discussions and always accesible - The chat is now persistent between discussions and always accessible
- The chat now tracks incoming and outcoming users in your conversation - The chat now tracks incoming and outcoming users in your conversation
- The chat allows your to see the visit card of users - The chat allows your to see the visit card of users
- You can close the chat window with the escape key - You can close the chat window with the escape key

View File

@ -104,6 +104,15 @@ export class GameRoom {
public getUserById(id: number): User | undefined { public getUserById(id: number): User | undefined {
return this.users.get(id); return this.users.get(id);
} }
public getUsersByUuid(uuid: string): User[] {
const userList: User[] = [];
for (const user of this.users.values()) {
if (user.uuid === uuid) {
userList.push(user);
}
}
return userList;
}
public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User { public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User {
const positionMessage = joinRoomMessage.getPositionmessage(); const positionMessage = joinRoomMessage.getPositionmessage();

View File

@ -21,7 +21,7 @@ interface ZoneDescriptor {
} }
export class PositionNotifier { export class PositionNotifier {
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!) // TODO: we need a way to clean the zones if no one is in the zone and no one listening (to free memory!)
private zones: Zone[][] = []; private zones: Zone[][] = [];

View File

@ -57,7 +57,7 @@ const roomManager: IRoomManagerServer = {
room = gameRoom; room = gameRoom;
user = myUser; user = myUser;
} else { } else {
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user. //Connection may have been closed before the init was finished, so we have to manually disconnect the user.
socketManager.leaveRoom(gameRoom, myUser); socketManager.leaveRoom(gameRoom, myUser);
} }
}) })

View File

@ -701,8 +701,8 @@ export class SocketManager {
return; return;
} }
const recipient = room.getUserByUuid(recipientUuid); const recipients = room.getUsersByUuid(recipientUuid);
if (recipient === undefined) { if (recipients.length === 0) {
console.error( console.error(
"In sendAdminMessage, could not find user with id '" + "In sendAdminMessage, could not find user with id '" +
recipientUuid + recipientUuid +
@ -711,14 +711,16 @@ export class SocketManager {
return; return;
} }
const sendUserMessage = new SendUserMessage(); for (const recipient of recipients) {
sendUserMessage.setMessage(message); const sendUserMessage = new SendUserMessage();
sendUserMessage.setType("ban"); //todo: is the type correct? sendUserMessage.setMessage(message);
sendUserMessage.setType("ban"); //todo: is the type correct?
const serverToClientMessage = new ServerToClientMessage(); const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage); serverToClientMessage.setSendusermessage(sendUserMessage);
recipient.socket.write(serverToClientMessage); recipient.socket.write(serverToClientMessage);
}
} }
public async banUser(roomId: string, recipientUuid: string, message: string): Promise<void> { public async banUser(roomId: string, recipientUuid: string, message: string): Promise<void> {
@ -732,8 +734,8 @@ export class SocketManager {
return; return;
} }
const recipient = room.getUserByUuid(recipientUuid); const recipients = room.getUsersByUuid(recipientUuid);
if (recipient === undefined) { if (recipients.length === 0) {
console.error( console.error(
"In banUser, could not find user with id '" + "In banUser, could not find user with id '" +
recipientUuid + recipientUuid +
@ -742,19 +744,21 @@ export class SocketManager {
return; return;
} }
// Let's leave the room now. for (const recipient of recipients) {
room.leave(recipient); // Let's leave the room now.
room.leave(recipient);
const banUserMessage = new BanUserMessage(); const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message); banUserMessage.setMessage(message);
banUserMessage.setType("banned"); banUserMessage.setType("banned");
const serverToClientMessage = new ServerToClientMessage(); const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setBanusermessage(banUserMessage); serverToClientMessage.setBanusermessage(banUserMessage);
// Let's close the connection when the user is banned. // Let's close the connection when the user is banned.
recipient.socket.write(serverToClientMessage); recipient.socket.write(serverToClientMessage);
recipient.socket.end(); recipient.socket.end();
}
} }
async sendAdminRoomMessage(roomId: string, message: string) { async sendAdminRoomMessage(roomId: string, message: string) {

View File

@ -56,7 +56,7 @@ A few things to notice:
## Building walls and "collidable" areas ## Building walls and "collidable" areas
By default, the characters can traverse any tiles. If you want to prevent your characeter from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile. By default, the characters can traverse any tiles. If you want to prevent your character from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile.
To make a tile "collidable", you should: To make a tile "collidable", you should:

View File

@ -3,9 +3,12 @@
import { chatMessagesStore, chatVisibilityStore } from "../../Stores/ChatStore"; import { chatMessagesStore, chatVisibilityStore } from "../../Stores/ChatStore";
import ChatMessageForm from './ChatMessageForm.svelte'; import ChatMessageForm from './ChatMessageForm.svelte';
import ChatElement from './ChatElement.svelte'; import ChatElement from './ChatElement.svelte';
import { afterUpdate, beforeUpdate } from "svelte"; import {afterUpdate, beforeUpdate} from "svelte";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
let listDom: HTMLElement; let listDom: HTMLElement;
let chatWindowElement: HTMLElement;
let handleFormBlur: { blur():void };
let autoscroll: boolean; let autoscroll: boolean;
beforeUpdate(() => { beforeUpdate(() => {
@ -16,6 +19,12 @@
if (autoscroll) listDom.scrollTo(0, listDom.scrollHeight); if (autoscroll) listDom.scrollTo(0, listDom.scrollHeight);
}); });
function onClick(event: MouseEvent) {
if (HtmlUtils.isClickedOutside(event, chatWindowElement)) {
handleFormBlur.blur();
}
}
function closeChat() { function closeChat() {
chatVisibilityStore.set(false); chatVisibilityStore.set(false);
} }
@ -26,10 +35,10 @@
} }
</script> </script>
<svelte:window on:keydown={onKeyDown}/> <svelte:window on:keydown={onKeyDown} on:click={onClick}/>
<aside class="chatWindow" transition:fly="{{ x: -1000, duration: 500 }}"> <aside class="chatWindow" transition:fly="{{ x: -1000, duration: 500 }}" bind:this={chatWindowElement}>
<p class="close-icon" on:click={closeChat}>&times</p> <p class="close-icon" on:click={closeChat}>&times</p>
<section class="messagesList" bind:this={listDom}> <section class="messagesList" bind:this={listDom}>
<ul> <ul>
@ -40,7 +49,7 @@
</ul> </ul>
</section> </section>
<section class="messageForm"> <section class="messageForm">
<ChatMessageForm></ChatMessageForm> <ChatMessageForm bind:handleForm={handleFormBlur}></ChatMessageForm>
</section> </section>
</aside> </aside>

View File

@ -7,13 +7,14 @@
export let message: ChatMessage; export let message: ChatMessage;
export let line: number; export let line: number;
const chatStyleLink = "color: white; text-decoration: underline;";
$: author = message.author as PlayerInterface; $: author = message.author as PlayerInterface;
$: targets = message.targets || []; $: targets = message.targets || [];
$: texts = message.text || []; $: texts = message.text || [];
function urlifyText(text: string): string { function urlifyText(text: string): string {
return HtmlUtils.urlify(text) return HtmlUtils.urlify(text, chatStyleLink);
} }
function renderDate(date: Date) { function renderDate(date: Date) {
return date.toLocaleTimeString(navigator.language, { return date.toLocaleTimeString(navigator.language, {

View File

@ -1,6 +1,12 @@
<script lang="ts"> <script lang="ts">
import {chatMessagesStore, chatInputFocusStore} from "../../Stores/ChatStore"; import {chatMessagesStore, chatInputFocusStore} from "../../Stores/ChatStore";
export const handleForm = {
blur() {
inputElement.blur();
}
}
let inputElement: HTMLElement;
let newMessageText = ''; let newMessageText = '';
function onFocus() { function onFocus() {
@ -18,7 +24,7 @@
</script> </script>
<form on:submit|preventDefault={saveMessage}> <form on:submit|preventDefault={saveMessage}>
<input type="text" bind:value={newMessageText} placeholder="Enter your message..." on:focus={onFocus} on:blur={onBlur} > <input type="text" bind:value={newMessageText} placeholder="Enter your message..." on:focus={onFocus} on:blur={onBlur} bind:this={inputElement}>
<button type="submit"> <button type="submit">
<img src="/static/images/send.png" alt="Send" width="20"> <img src="/static/images/send.png" alt="Send" width="20">
</button> </button>

View File

@ -107,7 +107,7 @@ export const createLoadingPromise = (
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
const errorCallback = (file: { src: string }) => { const errorCallback = (file: { src: string }) => {
if (file.src !== playerResourceDescriptor.img) return; if (file.src !== playerResourceDescriptor.img) return;
console.error("failed loading player ressource: ", playerResourceDescriptor); console.error("failed loading player resource: ", playerResourceDescriptor);
rej(playerResourceDescriptor); rej(playerResourceDescriptor);
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
loadPlugin.off("loaderror", errorCallback); loadPlugin.off("loaderror", errorCallback);

View File

@ -193,7 +193,7 @@ export class GameScene extends DirtyScene {
private popUpElements: Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>(); private popUpElements: Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
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 happening at the same time.
private emoteManager!: EmoteManager; private emoteManager!: EmoteManager;
private preloading: boolean = true; private preloading: boolean = true;
private startPositionCalculator!: StartPositionCalculator; private startPositionCalculator!: StartPositionCalculator;
@ -435,7 +435,7 @@ export class GameScene extends DirtyScene {
this.characterLayers = gameManager.getCharacterLayers(); this.characterLayers = gameManager.getCharacterLayers();
this.companion = gameManager.getCompanion(); this.companion = gameManager.getCompanion();
//initalise map //initialise map
this.Map = this.add.tilemap(this.MapUrlFile); this.Map = this.add.tilemap(this.MapUrlFile);
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/")); const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => { this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {

View File

@ -45,7 +45,7 @@ export class StartPositionCalculator {
/** /**
* *
* @param selectedLayer this is always the layer that is selected with the hash in the url * @param selectedLayer this is always the layer that is selected with the hash in the url
* @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} didnt yield any start points * @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} did not yield any start points
*/ */
public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) { public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) {
if (!selectedOrDefaultLayer) { if (!selectedOrDefaultLayer) {
@ -73,7 +73,7 @@ export class StartPositionCalculator {
/** /**
* *
* @param selectedLayer this is always the layer that is selected with the hash in the url * @param selectedLayer this is always the layer that is selected with the hash in the url
* @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} didnt yield any start points * @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} did not yield any start points
*/ */
private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface { private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface {
const tiles = selectedOrDefaultLayer.data; const tiles = selectedOrDefaultLayer.data;

View File

@ -244,6 +244,7 @@ export class CustomizeScene extends AbstractCharacterScene {
update(time: number, delta: number): void { update(time: number, delta: number): void {
if (this.lazyloadingAttempt) { if (this.lazyloadingAttempt) {
this.moveLayers(); this.moveLayers();
this.doMoveCursorHorizontally(this.moveHorizontally);
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
} }

View File

@ -21,7 +21,7 @@ export enum UserInputEvent {
} }
//we cannot use a map structure so we have to create a replacment //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>();

View File

@ -274,12 +274,12 @@ export const mediaStreamConstraintsStore = derived(
currentAudioConstraint = false; currentAudioConstraint = false;
} }
// Disable webcam for privacy reasons (the game is not visible and we were talking to noone) // Disable webcam for privacy reasons (the game is not visible and we were talking to no one)
if ($privacyShutdownStore === true) { if ($privacyShutdownStore === true) {
currentVideoConstraint = false; currentVideoConstraint = false;
} }
// Disable webcam for energy reasons (the user is not moving and we are talking to noone) // Disable webcam for energy reasons (the user is not moving and we are talking to no one)
if ($cameraEnergySavingStore === true) { if ($cameraEnergySavingStore === true) {
currentVideoConstraint = false; currentVideoConstraint = false;
currentAudioConstraint = false; currentAudioConstraint = false;

View File

@ -31,7 +31,7 @@ export class HtmlUtils {
return p.innerHTML; return p.innerHTML;
} }
public static urlify(text: string): string { public static urlify(text: string, style: string = ""): string {
const urlRegex = /(https?:\/\/[^\s]+)/g; const urlRegex = /(https?:\/\/[^\s]+)/g;
text = HtmlUtils.escapeHtml(text); text = HtmlUtils.escapeHtml(text);
return text.replace(urlRegex, (url: string) => { return text.replace(urlRegex, (url: string) => {
@ -40,10 +40,19 @@ export class HtmlUtils {
link.target = "_blank"; link.target = "_blank";
const text = document.createTextNode(url); const text = document.createTextNode(url);
link.appendChild(text); link.appendChild(text);
link.setAttribute("style", style);
return link.outerHTML; return link.outerHTML;
}); });
} }
public static isClickedInside(event: MouseEvent, target: HTMLElement): boolean {
return !!event.composedPath().find((et) => et === target);
}
public static isClickedOutside(event: MouseEvent, target: HTMLElement): boolean {
return !this.isClickedInside(event, target);
}
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T { private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
return elem !== null; return elem !== null;
} }

View File

@ -295,7 +295,7 @@ export class SimplePeer {
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
peer.destroy(); peer.destroy();
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion //Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
/*if(!this.PeerScreenSharingConnectionArray.delete(userId)){ /*if(!this.PeerScreenSharingConnectionArray.delete(userId)){
throw 'Couln\'t delete peer screen sharing connexion'; throw 'Couln\'t delete peer screen sharing connexion';
}*/ }*/
@ -370,14 +370,14 @@ export class SimplePeer {
console.error( console.error(
'Could not find peer whose ID is "' + data.userId + '" in receiveWebrtcScreenSharingSignal' 'Could not find peer whose ID is "' + data.userId + '" in receiveWebrtcScreenSharingSignal'
); );
console.info("Attempt to create new peer connexion"); console.info("Attempt to create new peer connection");
if (stream) { if (stream) {
this.sendLocalScreenSharingStreamToUser(data.userId, stream); this.sendLocalScreenSharingStreamToUser(data.userId, stream);
} }
} }
} catch (e) { } catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e); console.error(`receiveWebrtcSignal => ${data.userId}`, e);
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion //Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
//this.PeerScreenSharingConnectionArray.delete(data.userId); //this.PeerScreenSharingConnectionArray.delete(data.userId);
this.receiveWebrtcScreenSharingSignal(data); this.receiveWebrtcScreenSharingSignal(data);
} }
@ -485,7 +485,7 @@ export class SimplePeer {
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) { if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
PeerConnectionScreenSharing.destroy(); PeerConnectionScreenSharing.destroy();
//Comment this peer connexion because if we delete and try to reshare screen, the RTCPeerConnection send renegociate event. This array will be remove when user left circle discussion //Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
//this.PeerScreenSharingConnectionArray.delete(userId); //this.PeerScreenSharingConnectionArray.delete(userId);
} }
} }

View File

@ -8,19 +8,19 @@ describe("getRessourceDescriptor()", () => {
expect(desc.img).toEqual('url'); expect(desc.img).toEqual('url');
}); });
it(", if given a string as parameter, should search trough 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 trough 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 trough 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

@ -176,7 +176,7 @@ Tuomo Untinen CC-BY-3.0
Casper Nilsson Casper Nilsson
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Asain themed shrine including red lantern - Asian themed shrine including red lantern
- foodog statue - foodog statue
- Toro - Toro
- Cherry blossom tree - Cherry blossom tree

View File

@ -21,7 +21,7 @@ interface ZoneDescriptor {
} }
export class PositionDispatcher { export class PositionDispatcher {
// TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!) // TODO: we need a way to clean the zones if no one is in the zone and no one listening (to free memory!)
private zones: Zone[][] = []; private zones: Zone[][] = [];

View File

@ -463,7 +463,7 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true); client.send(serverToClientMessage.serializeBinary().buffer, true);
} catch (e) { } catch (e) {
console.error("An error occured while generating the Jitsi JWT token: ", e); console.error("An error occurred while generating the Jitsi JWT token: ", e);
} }
} }