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

This commit is contained in:
_Bastler 2021-06-15 08:18:39 +02:00
commit 39dbd56207
30 changed files with 273 additions and 204 deletions

View File

@ -149,6 +149,37 @@ jobs:
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@3.1.0
- name: Write certificate
run: echo "${CERTS_PRIVATE_KEY}" > secret.key && chmod 0600 secret.key
env:
CERTS_PRIVATE_KEY: ${{ secrets.CERTS_PRIVATE_KEY }}
- name: Download certificate
run: mkdir secrets && scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i secret.key ubuntu@cert.workadventu.re:./config/live/workadventu.re/* secrets/
- name: Create namespace
uses: steebchen/kubectl@v1.0.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
with:
args: create namespace workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
continue-on-error: true
- name: Delete old certificates in namespace
uses: steebchen/kubectl@v1.0.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
with:
args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} delete secret certificate-tls
continue-on-error: true
- name: Install certificates in namespace
uses: steebchen/kubectl@v1.0.0
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
with:
args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} create secret tls certificate-tls --key="secrets/privkey.pem" --cert="secrets/fullchain.pem"
- name: Deploy
uses: thecodingmachine/deeployer-action@master
env:
@ -168,4 +199,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re

View File

@ -1,9 +1,6 @@
import {
BatchMessage,
PusherToBackMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {AdminSocket} from "../RoomManager";

View File

@ -108,6 +108,7 @@ export class GameRoom {
this.positionNotifier,
socket,
joinRoomMessage.getTagList(),
joinRoomMessage.getVisitcardurl(),
joinRoomMessage.getName(),
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()),
joinRoomMessage.getCompanion()

View File

@ -22,6 +22,7 @@ export class User implements Movable {
private positionNotifier: PositionNotifier,
public readonly socket: UserSocket,
public readonly tags: string[],
public readonly visitCardUrl: string|null,
public readonly name: string,
public readonly characterLayers: CharacterLayer[],
public readonly companion?: CompanionMessage

View File

@ -11,7 +11,7 @@ import {
JoinRoomMessage,
PlayGlobalMessage,
PusherToBackMessage,
QueryJitsiJwtMessage, RefreshRoomPromptMessage, RequestVisitCardMessage,
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SilentMessage,
@ -74,8 +74,6 @@ const roomManager: IRoomManagerServer = {
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
} else if (message.hasEmotepromptmessage()){
socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage);
} else if (message.hasRequestvisitcardmessage()) {
socketManager.handleRequestVisitCardMessage(room, user, message.getRequestvisitcardmessage() as RequestVisitCardMessage);
}else if (message.hasSendusermessage()) {
const sendUserMessage = message.getSendusermessage();
if(sendUserMessage !== undefined) {

View File

@ -1,22 +0,0 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios";
class AdminApi {
fetchVisitCardUrl(membershipUuid: string): Promise<string> {
if (ADMIN_API_URL) {
return Axios.get(ADMIN_API_URL + '/api/membership/'+membershipUuid,
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
).then((res) => {
return res.data;
}).catch(() => {
return 'INVALID';
});
} else {
return Promise.resolve('INVALID')
}
}
}
export const adminApi = new AdminApi();

View File

@ -27,7 +27,7 @@ import {
WorldFullWarningMessage,
UserLeftZoneMessage,
EmoteEventMessage,
BanUserMessage, RefreshRoomMessage, EmotePromptMessage, RequestVisitCardMessage, VisitCardMessage,
BanUserMessage, RefreshRoomMessage, EmotePromptMessage,
} from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -51,7 +51,6 @@ import {Zone} from "_Model/Zone";
import Debug from "debug";
import {Admin} from "_Model/Admin";
import crypto from "crypto";
import {adminApi} from "./AdminApi";
const debug = Debug('sockermanager');
@ -300,6 +299,9 @@ export class SocketManager {
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
if (thing.visitCardUrl) {
userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl);
}
userJoinedZoneMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
@ -605,6 +607,9 @@ export class SocketManager {
userJoinedMessage.setName(thing.name);
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
if (thing.visitCardUrl) {
userJoinedMessage.setVisitcardurl(thing.visitCardUrl);
}
userJoinedMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
@ -770,21 +775,6 @@ export class SocketManager {
emoteEventMessage.setActoruserid(user.id);
room.emitEmoteEvent(user, emoteEventMessage);
}
async handleRequestVisitCardMessage(room: GameRoom, user: User, requestvisitcardmessage: RequestVisitCardMessage): Promise<void> {
const targetUser = room.getUserById(requestvisitcardmessage.getTargetuserid());
if (!targetUser) {
throw 'Could not find user for id '+requestvisitcardmessage.getTargetuserid();
}
const url = await adminApi.fetchVisitCardUrl(targetUser.uuid);
const visitCardMessage = new VisitCardMessage();
visitCardMessage.setUrl(url);
const clientMessage = new ServerToClientMessage();
clientMessage.setVisitcardmessage(visitCardMessage);
user.socket.write(clientMessage);
}
}
export const socketManager = new SocketManager();

View File

@ -1,10 +1,6 @@
import "jasmine";
import {GameRoom, ConnectCallback, DisconnectCallback } from "_Model/GameRoom";
import {Point} from "../src/Model/Websocket/MessageUserPosition";
import { Group } from "../src/Model/Group";
import {PositionNotifier} from "../src/Model/PositionNotifier";
import {User, UserSocket} from "../src/Model/User";
import {PointInterface} from "../src/Model/Websocket/PointInterface";
import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface";
@ -30,14 +26,14 @@ describe("PositionNotifier", () => {
y: 500,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
const user2 = new User(2, 'test', '10.0.0.2', {
x: -9999,
y: -9999,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
@ -105,14 +101,14 @@ describe("PositionNotifier", () => {
y: 500,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
const user2 = new User(2, 'test', '10.0.0.2', {
x: 0,
y: 0,
moving: false,
direction: 'down'
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
const listener = {} as ZoneSocket;
positionNotifier.addZoneListener(listener, 0, 0);

View File

@ -11,8 +11,7 @@
"back1": {
"image": "thecodingmachine/workadventure-back:"+tag,
"host": {
"url": "api1."+url,
"https": "enable",
"url": "api1-"+url,
"containerPort": 8080
},
"ports": [8080, 50051],
@ -30,8 +29,7 @@
"back2": {
"image": "thecodingmachine/workadventure-back:"+tag,
"host": {
"url": "api2."+url,
"https": "enable",
"url": "api2-"+url,
"containerPort": 8080
},
"ports": [8080, 50051],
@ -50,8 +48,7 @@
"replicas": 2,
"image": "thecodingmachine/workadventure-pusher:"+tag,
"host": {
"url": "pusher."+url,
"https": "enable"
"url": "pusher-"+url,
},
"ports": [8080],
"env": {
@ -68,27 +65,25 @@
"front": {
"image": "thecodingmachine/workadventure-front:"+tag,
"host": {
"url": "play."+url,
"https": "enable"
"url": "play-"+url,
},
"ports": [80],
"env": {
"PUSHER_URL": "//pusher."+url,
"UPLOADER_URL": "//uploader."+url,
"PUSHER_URL": "//pusher-"+url,
"UPLOADER_URL": "//uploader-"+url,
"ADMIN_URL": "//"+url,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
"START_ROOM_URL": "/_/global/maps-"+url+"/Floor0/floor0.json"
//"GA_TRACKING_ID": "UA-10196481-11"
}
},
"uploader": {
"image": "thecodingmachine/workadventure-uploader:"+tag,
"host": {
"url": "uploader."+url,
"https": "enable",
"url": "uploader-"+url,
"containerPort": 8080
},
"ports": [8080],
@ -98,16 +93,12 @@
"maps": {
"image": "thecodingmachine/workadventure-maps:"+tag,
"host": {
"url": "maps."+url,
"https": "enable"
"url": "maps-"+url
},
"ports": [80]
},
},
"config": {
"https": {
"mail": "d.negrier@thecodingmachine.com"
},
k8sextension(k8sConf)::
k8sConf + {
back1+: {
@ -122,6 +113,14 @@
}
}
}
},
ingress+: {
spec+: {
tls+: [{
hosts: ["api1-"+url],
secretName: "certificate-tls"
}]
}
}
},
back2+: {
@ -136,6 +135,14 @@
}
}
}
},
ingress+: {
spec+: {
tls+: [{
hosts: ["api2-"+url],
secretName: "certificate-tls"
}]
}
}
},
pusher+: {
@ -150,8 +157,46 @@
}
}
}
}
}
},
ingress+: {
spec+: {
tls+: [{
hosts: ["pusher-"+url],
secretName: "certificate-tls"
}]
}
}
},
front+: {
ingress+: {
spec+: {
tls+: [{
hosts: ["play-"+url],
secretName: "certificate-tls"
}]
}
}
},
uploader+: {
ingress+: {
spec+: {
tls+: [{
hosts: ["uploader-"+url],
secretName: "certificate-tls"
}]
}
}
},
maps+: {
ingress+: {
spec+: {
tls+: [{
hosts: ["maps-"+url],
secretName: "certificate-tls"
}]
}
}
},
}
}
}

View File

@ -1,39 +1,64 @@
<script lang="typescript">
import { fly } from 'svelte/transition';
import {requestVisitCardsStore} from "../../Stores/GameStore";
import {onMount} from "svelte";
export let visitCardUrl: string;
let w = '500px';
let h = '250px';
let hidden = true;
let cvIframe;
function closeCard() {
requestVisitCardsStore.set(null);
}
function handleIframeMessage(message:any) {
if (message.data.type === 'cvIframeSize') {
w = (message.data.data.w) + 'px';
h = (message.data.data.h) + 'px';
}
}
onMount(() => {
cvIframe.onload = () => hidden = false
cvIframe.onerror = () => hidden = false
})
</script>
<style lang="scss">
.loader {
border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 120px;
height: 120px;
margin:auto;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.visitCard {
pointer-events: all;
margin-left: auto;
margin-right: auto;
width: 530px;
margin-top: 200px;
max-width: 100vw;
.defaultCard {
border-radius: 5px;
border: 2px black solid;
background-color: whitesmoke;
width: 530px;
header {
padding: 5px;
}
}
max-width: 80vw;
iframe {
border: 0;
width: 530px;
height: 270px;
max-width: 80vw;
overflow: hidden;
&.hidden {
visibility: hidden;
position: absolute;
}
}
button {
@ -43,22 +68,17 @@
</style>
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}">
{#if visitCardUrl === 'INVALID'}
<div class="defaultCard">
<header>
<h2>Sorry</h2>
<p style="font-style: italic;">This user doesn't have a contact card.</p>
</header>
<main style="padding: 5px; background-color: gray">
<p>Maybe he is offline, or this feature is deactivated.</p>
</main>
</div>
{:else}
<iframe title="visitCardTitle" src={visitCardUrl}></iframe>
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}" style="width: {w}">
{#if hidden}
<div class="loader"></div>
{/if}
<div class="buttonContainer">
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
</div>
<iframe title="visitCard" src={visitCardUrl} allow="clipboard-read; clipboard-write self {visitCardUrl}" style="width: {w}; height: {h}" class:hidden={hidden} bind:this={cvIframe}></iframe>
{#if !hidden}
<div class="buttonContainer">
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
</div>
{/if}
</section>
<svelte:window on:message={handleIframeMessage}/>

View File

@ -1,5 +1,3 @@
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import type {SignalData} from "simple-peer";
import type {RoomConnection} from "./RoomConnection";
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
@ -47,6 +45,7 @@ export interface MessageUserPositionInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
visitCardUrl: string|null;
companion: string|null;
}
@ -60,6 +59,7 @@ export interface MessageUserJoined {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
visitCardUrl: string | null;
companion: string|null;
}
@ -85,11 +85,6 @@ export interface WebRtcSignalReceivedMessageInterface {
webRtcPassword: string | undefined
}
export interface StartMapInterface {
mapUrlStart: string,
startInstance: string
}
export interface ViewportInterface {
left: number,
top: number,

View File

@ -30,7 +30,7 @@ import {
EmoteEventMessage,
EmotePromptMessage,
SendUserMessage,
BanUserMessage, RequestVisitCardMessage
BanUserMessage,
} from "../Messages/generated/messages_pb"
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -50,7 +50,6 @@ import {worldFullMessageStream} from "./WorldFullMessageStream";
import {worldFullWarningStream} from "./WorldFullWarningStream";
import {connectionManager} from "./ConnectionManager";
import {emoteEventStream} from "./EmoteEventStream";
import {requestVisitCardsStore} from "../Stores/GameStore";
const manualPingDelay = 20000;
@ -204,8 +203,6 @@ export class RoomConnection implements RoomConnection {
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
} else if (message.hasWorldfullwarningmessage()) {
worldFullWarningStream.onMessage();
} else if (message.hasVisitcardmessage()) {
requestVisitCardsStore.set(message?.getVisitcardmessage()?.getUrl() as unknown as string);
} else if (message.hasRefreshroommessage()) {
//todo: implement a way to notify the user the room was refreshed.
} else {
@ -347,6 +344,7 @@ export class RoomConnection implements RoomConnection {
userId: message.getUserid(),
name: message.getName(),
characterLayers,
visitCardUrl: message.getVisitcardurl(),
position: ProtobufClientUtils.toPointInterface(position),
companion: companion ? companion.getName() : null
}
@ -620,14 +618,4 @@ export class RoomConnection implements RoomConnection {
this.socket.send(clientToServerMessage.serializeBinary().buffer);
}
public requestVisitCardUrl(targetUserId: number): void {
const message = new RequestVisitCardMessage();
message.setTargetuserid(targetUserId);
const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setRequestvisitcardmessage(message);
this.socket.send(clientToServerMessage.serializeBinary().buffer);
}
}

View File

@ -10,7 +10,6 @@ const TURN_USER: string = process.env.TURN_USER || '';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
export const DISABLE_VISIT_CARD_FEATURE : boolean = process.env.DISABLE_VISIT_CARD_FEATURE == "true";
const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;

View File

@ -41,6 +41,7 @@ export abstract class Character extends Container {
direction: PlayerAnimationDirections,
moving: boolean,
frame: string | number,
isClickable: boolean,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
@ -60,7 +61,7 @@ export abstract class Character extends Container {
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName);
if (this.isClickable()) {
if (isClickable) {
this.setInteractive({
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
@ -79,7 +80,7 @@ export abstract class Character extends Container {
this.setDepth(-1);
this.playAnimation(direction, moving);
if (typeof companion === 'string') {
this.addCompanion(companion, companionTexturePromise);
}
@ -90,12 +91,10 @@ export abstract class Character extends Container {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
}
}
public abstract isClickable(): boolean;
public addTextures(textures: string[], frame?: string | number): void {
for (const texture of textures) {
if(!this.scene.textures.exists(texture)){
if(this.scene && !this.scene.textures.exists(texture)){
throw new TextureError('texture not found');
}
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
@ -240,23 +239,23 @@ export abstract class Character extends Container {
this.scene.sys.updateList.remove(sprite);
}
}
this.list.forEach(objectContaining => objectContaining.destroy())
this.list.forEach(objectContaining => objectContaining.destroy())
super.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);
}

View File

@ -0,0 +1,20 @@
import Container = Phaser.GameObjects.Container;
import type {Scene} from "phaser";
import Sprite = Phaser.GameObjects.Sprite;
/**
* A sprite of a customized character (used in the Customize Scene only)
*/
export class CustomizedCharacter extends Container {
public constructor(scene: Scene, x: number, y: number, layers: string[]) {
super(scene, x, y);
this.updateSprites(layers);
}
public updateSprites(layers: string[]): void {
this.removeAll(true);
for (const layer of layers) {
this.add(new Sprite(this.scene, 0, 0, layer));
}
}
}

View File

@ -2,15 +2,15 @@ import type {GameScene} from "../Game/GameScene";
import type {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import type {PlayerAnimationDirections} from "../Player/Animation";
import {DISABLE_VISIT_CARD_FEATURE} from "../../Enum/EnvironmentVariable";
import {requestVisitCardsStore} from "../../Stores/GameStore";
export const playerClickedEvent = 'playerClickedEvent';
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
*/
export class RemotePlayer extends Character {
userId: number;
private visitCardUrl: string|null;
constructor(
userId: number,
@ -21,16 +21,18 @@ export class RemotePlayer extends Character {
texturesPromise: Promise<string[]>,
direction: PlayerAnimationDirections,
moving: boolean,
visitCardUrl: string|null,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
super(Scene, x, y, texturesPromise, name, direction, moving, 1, !!visitCardUrl, companion, companionTexturePromise);
//set data
this.userId = userId;
this.visitCardUrl = visitCardUrl;
this.on('pointerdown', () => {
this.emit(playerClickedEvent, this.userId);
requestVisitCardsStore.set(this.visitCardUrl);
})
}
@ -45,8 +47,4 @@ export class RemotePlayer extends Character {
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
}
}
isClickable(): boolean {
return !DISABLE_VISIT_CARD_FEATURE; //todo: make remote players clickable if they are logged in.
}
}

View File

@ -6,5 +6,6 @@ export interface AddPlayerInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
visitCardUrl: string|null;
companion: string|null;
}

View File

@ -21,7 +21,6 @@ import type {
ITiledMapLayer,
ITiledMapLayerProperty,
ITiledMapObject,
ITiledText,
ITiledMapTileLayer,
ITiledTileSet
} from "../Map/ITiledMap";
@ -29,7 +28,7 @@ import type {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
import {playerClickedEvent, RemotePlayer} from "../Entity/RemotePlayer";
import {RemotePlayer} from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
@ -576,6 +575,7 @@ export class GameScene extends DirtyScene implements CenterListener {
characterLayers: message.characterLayers,
name: message.name,
position: message.position,
visitCardUrl: message.visitCardUrl,
companion: message.companion
}
this.addPlayer(userMessage);
@ -1415,12 +1415,10 @@ export class GameScene extends DirtyScene implements CenterListener {
texturesPromise,
addPlayerData.position.direction as PlayerAnimationDirections,
addPlayerData.position.moving,
addPlayerData.visitCardUrl,
addPlayerData.companion,
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
);
player.on(playerClickedEvent, (userID:number) => {
this.connection?.requestVisitCardUrl(userID);
})
this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position);

View File

@ -15,6 +15,7 @@ import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStor
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
import {CustomizedCharacter} from "../Entity/CustomizedCharacter";
export const CustomizeSceneName = "CustomizeScene";
@ -25,12 +26,15 @@ export class CustomizeScene extends AbstractCharacterScene {
private Rectangle!: Rectangle;
private selectedLayers: number[] = [0];
private containersRow: Container[][] = [];
private containersRow: CustomizedCharacter[][] = [];
public activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = [];
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
private moveHorizontally: number = 0;
private moveVertically: number = 0;
constructor() {
super({
key: CustomizeSceneName
@ -88,10 +92,13 @@ export class CustomizeScene extends AbstractCharacterScene {
this.backToPreviousScene();
});
this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1));
this.input.keyboard.on('keyup-LEFT', () => this.moveCursorHorizontally(-1));
this.input.keyboard.on('keyup-DOWN', () => this.moveCursorVertically(1));
this.input.keyboard.on('keyup-UP', () => this.moveCursorVertically(-1));
// Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
// because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
// explain, the list of sprites managed by the update list become immense
this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1);
this.input.keyboard.on('keyup-LEFT', () => this.moveHorizontally = -1);
this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1);
this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1);
const customCursorPosition = localUserStore.getCustomCursorPosition();
if (customCursorPosition) {
@ -104,7 +111,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.onResize();
}
public moveCursorHorizontally(index: number): void {
public doMoveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index;
if (this.selectedLayers[this.activeRow] < 0) {
this.selectedLayers[this.activeRow] = 0
@ -116,7 +123,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.saveInLocalStorage();
}
public moveCursorVertically(index:number): void {
public doMoveCursorVertically(index:number): void {
this.activeRow += index;
if (this.activeRow < 0) {
@ -165,20 +172,20 @@ export class CustomizeScene extends AbstractCharacterScene {
* @param selectedItem, The number of the item select (0 for black body...)
*/
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
return new Container(this, x, y,this.getContainerChildren(layerNumber,selectedItem));
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem));
}
private getContainerChildren(layerNumber: number, selectedItem: number): Array<Sprite> {
const children: Array<Sprite> = new Array<Sprite>();
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
const children: Array<string> = new Array<string>();
for (let j = 0; j <= layerNumber; j++) {
if (j === layerNumber) {
children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name));
children.push(this.layers[j][selectedItem].name);
} else {
const layer = this.selectedLayers[j];
if (layer === undefined) {
continue;
}
children.push(this.generateLayers(0, 0, this.layers[j][layer].name));
children.push(this.layers[j][layer].name);
}
}
return children;
@ -215,15 +222,15 @@ export class CustomizeScene extends AbstractCharacterScene {
* @return a new sprite
*/
private generateLayers(x: number, y: number, name: string): Sprite {
return new Sprite(this, x, y, name);
//return new Sprite(this, x, y, name);
return this.add.sprite(0, 0, name);
}
private updateSelectedLayer() {
for(let i = 0; i < this.containersRow.length; i++){
for(let j = 0; j < this.containersRow[i].length; j++){
const children = this.getContainerChildren(i, j);
this.containersRow[i][j].removeAll(true);
this.containersRow[i][j].add(children);
const children = this.getContainerChildren(i, j);
this.containersRow[i][j].updateSprites(children);
}
}
}
@ -234,6 +241,15 @@ export class CustomizeScene extends AbstractCharacterScene {
this.moveLayers();
this.lazyloadingAttempt = false;
}
if (this.moveHorizontally !== 0) {
this.doMoveCursorHorizontally(this.moveHorizontally);
this.moveHorizontally = 0;
}
if (this.moveVertically !== 0) {
this.doMoveCursorVertically(this.moveVertically);
this.moveVertically = 0;
}
}

View File

@ -124,10 +124,15 @@ export class SelectCharacterScene extends AbstractCharacterScene {
selectCharacterSceneVisibleStore.set(false);
}
createCurrentPlayer(): void {
createCurrentPlayer(): void {
for (let i = 0; i <this.playerModels.length; i++) {
const playerResource = this.playerModels[i];
//check already exist texture
if(this.players.find((c) => c.texture.key === playerResource.name)){
continue;
}
const [middleX, middleY] = this.getCharacterPosition();
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
this.setUpPlayer(player, i);
@ -258,6 +263,8 @@ export class SelectCharacterScene extends AbstractCharacterScene {
}
if(this.lazyloadingAttempt){
//re-render players list
this.createCurrentPlayer();
this.moveUser();
this.lazyloadingAttempt = false;
}

View File

@ -26,7 +26,7 @@ export class Player extends Character {
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
//the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false);
@ -102,10 +102,6 @@ export class Player extends Character {
}
}
isClickable(): boolean {
return true;
}
openEmoteMenu(emotes:RadialMenuItem[]): void {
this.cancelPreviousEmote();
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)

View File

@ -39,10 +39,11 @@ class WaScaleManager {
const style = this.scaleManager.canvas.style;
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
// Note: onResize will be called twice (once here and once is Game.ts), but we have no better way.
// Note: onResize will be called twice (once here and once in Game.ts), but we have no better way.
for (const scene of this.game.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize();
// We are delaying the call to the "render" event because otherwise, the "camera" coordinates are not correctly updated.
scene.events.once(Phaser.Scenes.Events.RENDER, () => scene.onResize());
}
}

View File

@ -169,6 +169,7 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
return;
} catch (e) {
currentStream = null;
requestedScreenSharingState.disableScreenSharing();
console.info("Error. Unable to share screen.", e);
set({
type: 'error',

View File

@ -379,6 +379,7 @@ body {
}
#game {
height: 100%;
width: 100%;
position: relative;
/* Position relative is needed for the game-overlay. */

View File

@ -75,14 +75,6 @@ message EmoteEventMessage {
string emote = 2;
}
message RequestVisitCardMessage {
int32 targetUserId = 1;
}
message VisitCardMessage {
string url = 1;
}
message QueryJitsiJwtMessage {
string jitsiRoom = 1;
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
@ -102,7 +94,6 @@ message ClientToServerMessage {
ReportPlayerMessage reportPlayerMessage = 11;
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
EmotePromptMessage emotePromptMessage = 13;
RequestVisitCardMessage requestVisitCardMessage = 14;
}
}
@ -166,6 +157,7 @@ message UserJoinedMessage {
repeated CharacterLayerMessage characterLayers = 3;
PositionMessage position = 4;
CompanionMessage companion = 5;
string visitCardUrl = 6;
}
message UserLeftMessage {
@ -268,7 +260,6 @@ message ServerToClientMessage {
RefreshRoomMessage refreshRoomMessage = 17;
WorldConnexionMessage worldConnexionMessage = 18;
EmoteEventMessage emoteEventMessage = 19;
VisitCardMessage visitCardMessage = 20;
}
}
@ -284,6 +275,7 @@ message JoinRoomMessage {
repeated string tag = 6;
string IPAddress = 7;
CompanionMessage companion = 8;
string visitCardUrl = 9;
}
message UserJoinedZoneMessage {
@ -293,6 +285,7 @@ message UserJoinedZoneMessage {
PositionMessage position = 4;
Zone fromZone = 5;
CompanionMessage companion = 6;
string visitCardUrl = 7;
}
message UserLeftZoneMessage {
@ -340,7 +333,6 @@ message PusherToBackMessage {
SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13;
EmotePromptMessage emotePromptMessage = 14;
RequestVisitCardMessage requestVisitCardMessage = 15;
}
}

View File

@ -1,5 +1,5 @@
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import {GameRoomPolicyTypes, PusherRoom} from "../Model/PusherRoom";
import {GameRoomPolicyTypes} from "../Model/PusherRoom";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {
SetPlayerDetailsMessage,
@ -12,13 +12,11 @@ import {
WebRtcSignalToServerMessage,
PlayGlobalMessage,
ReportPlayerMessage,
EmoteEventMessage,
QueryJitsiJwtMessage,
SendUserMessage,
ServerToClientMessage,
CompanionMessage,
EmotePromptMessage,
RequestVisitCardMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js"
@ -26,7 +24,7 @@ import {parse} from "query-string";
import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {SocketManager, socketManager} from "../Services/SocketManager";
import {emitError, emitInBatch} from "../Services/IoSocketHelpers";
import {emitInBatch} from "../Services/IoSocketHelpers";
import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable";
import {Zone} from "_Model/Zone";
import {ExAdminSocketInterface} from "_Model/Websocket/ExAdminSocketInterface";
@ -169,6 +167,7 @@ export class IoSocketController {
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = [];
let memberVisitCardUrl: string|null = null;
let memberMessages: unknown;
let memberTextures: CharacterTexture[] = [];
const room = await socketManager.getOrCreateRoom(roomId);
@ -177,6 +176,7 @@ export class IoSocketController {
let userData : FetchMemberDataByUuidResponse = {
uuid: v4(),
tags: [],
visitCardUrl: null,
textures: [],
messages: [],
anonymous: true
@ -204,6 +204,7 @@ export class IoSocketController {
}
memberMessages = userData.messages;
memberTags = userData.tags;
memberVisitCardUrl = userData.visitCardUrl;
memberTextures = userData.textures;
if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
throw new Error('Insufficient privileges to access this room')
@ -240,6 +241,7 @@ export class IoSocketController {
characterLayers: characterLayerObjs,
messages: memberMessages,
tags: memberTags,
visitCardUrl: memberVisitCardUrl,
textures: memberTextures,
position: {
x: x,
@ -338,9 +340,6 @@ export class IoSocketController {
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
} else if (message.hasEmotepromptmessage()){
socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage);
} else if (message.hasRequestvisitcardmessage()) {
socketManager.handleRequestVisitCardMessage(client, message.getRequestvisitcardmessage() as RequestVisitCardMessage);
}
/* Ok is false if backpressure was built up, wait for drain */
@ -381,6 +380,7 @@ export class IoSocketController {
client.messages = ws.messages;
client.name = ws.name;
client.tags = ws.tags;
client.visitCardUrl = ws.visitCardUrl;
client.textures = ws.textures;
client.characterLayers = ws.characterLayers;
client.companion = ws.companion;

View File

@ -40,6 +40,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
disconnecting: boolean,
messages: unknown,
tags: string[],
visitCardUrl: string|null,
textures: CharacterTexture[],
backConnection: BackConnection,
listenedZones: Set<Zone>;

View File

@ -30,7 +30,7 @@ export type MovesCallback = (thing: Movable, position: PositionInterface, listen
export type LeavesCallback = (thing: Movable, listener: User) => void;*/
export class UserDescriptor {
private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private companion?: CompanionMessage) {
private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private visitCardUrl: string | null, private companion?: CompanionMessage) {
if (!Number.isInteger(this.userId)) {
throw new Error('UserDescriptor.userId is not an integer: '+this.userId);
}
@ -41,7 +41,7 @@ export class UserDescriptor {
if (position === undefined) {
throw new Error('Missing position');
}
return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position, message.getCompanion());
return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position, message.getVisitcardurl(), message.getCompanion());
}
public update(userMovedMessage: UserMovedMessage) {
@ -59,7 +59,10 @@ export class UserDescriptor {
userJoinedMessage.setName(this.name);
userJoinedMessage.setCharacterlayersList(this.characterLayers);
userJoinedMessage.setPosition(this.position);
userJoinedMessage.setCompanion(this.companion)
if (this.visitCardUrl) {
userJoinedMessage.setVisitcardurl(this.visitCardUrl);
}
userJoinedMessage.setCompanion(this.companion);
return userJoinedMessage;
}

View File

@ -36,6 +36,7 @@ export interface CharacterTexture {
export interface FetchMemberDataByUuidResponse {
uuid: string;
tags: string[];
visitCardUrl: string|null;
textures: CharacterTexture[];
messages: unknown[];
anonymous?: boolean;

View File

@ -30,7 +30,6 @@ import {
BanMessage,
RefreshRoomMessage,
EmotePromptMessage,
RequestVisitCardMessage
} from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
@ -162,6 +161,9 @@ export class SocketManager implements ZoneEventListener {
joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
if (client.visitCardUrl) {
joinRoomMessage.setVisitcardurl(client.visitCardUrl);
}
joinRoomMessage.setCompanion(client.companion);
for (const characterLayer of client.characterLayers) {
@ -604,13 +606,6 @@ export class SocketManager implements ZoneEventListener {
client.backConnection.write(pusherToBackMessage);
}
handleRequestVisitCardMessage(client: ExSocketInterface, requestVisitCardMessage: RequestVisitCardMessage) {
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setRequestvisitcardmessage(requestVisitCardMessage);
client.backConnection.write(pusherToBackMessage);
}
}
export const socketManager = new SocketManager();