Merge pull request #1099 from thecodingmachine/visitCard

FEATURE: clicking on another player show a contact card when possible
This commit is contained in:
Kharhamel 2021-06-03 18:40:45 +02:00 committed by GitHub
commit 44c2276952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 183 additions and 9 deletions

View File

@ -17,6 +17,11 @@
- now use the 'Press Start 2P' font family and added an outline - now use the 'Press Start 2P' font family and added an outline
- As a result, we can now allow non-standard letters like french accents or chinese characters! - As a result, we can now allow non-standard letters like french accents or chinese characters!
- Added the contact card feature. (@Kharhamel)
- Click on another player to see its contact info.
- Premium-only feature unfortunately. I need to find a way to make it available for all.
- If no contact data is found (either because the user is anonymous or because no admin backend), display an error card.
- Mobile support has been improved - Mobile support has been improved
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used - WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
- Mouse wheel support to zoom in / out - Mouse wheel support to zoom in / out

View File

@ -89,6 +89,9 @@ export class GameRoom {
public getUserByUuid(uuid: string): User|undefined { public getUserByUuid(uuid: string): User|undefined {
return this.usersByUuid.get(uuid); return this.usersByUuid.get(uuid);
} }
public getUserById(id: number): User|undefined {
return this.users.get(id);
}
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User { public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
const positionMessage = joinRoomMessage.getPositionmessage(); const positionMessage = joinRoomMessage.getPositionmessage();

View File

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

View File

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

View File

@ -27,7 +27,7 @@ import {
WorldFullWarningMessage, WorldFullWarningMessage,
UserLeftZoneMessage, UserLeftZoneMessage,
EmoteEventMessage, EmoteEventMessage,
BanUserMessage, RefreshRoomMessage, EmotePromptMessage, BanUserMessage, RefreshRoomMessage, EmotePromptMessage, RequestVisitCardMessage, VisitCardMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User"; import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -51,6 +51,7 @@ import {Zone} from "_Model/Zone";
import Debug from "debug"; import Debug from "debug";
import {Admin} from "_Model/Admin"; import {Admin} from "_Model/Admin";
import crypto from "crypto"; import crypto from "crypto";
import {adminApi} from "./AdminApi";
const debug = Debug('sockermanager'); const debug = Debug('sockermanager');
@ -769,6 +770,21 @@ export class SocketManager {
emoteEventMessage.setActoruserid(user.id); emoteEventMessage.setActoruserid(user.id);
room.emitEmoteEvent(user, emoteEventMessage); room.emitEmoteEvent(user, emoteEventMessage);
} }
async handleRequestVisitCardMessage(room: GameRoom, user: User, requestvisitcardmessage: RequestVisitCardMessage): Promise<void> {
const targetUser = room.getUserById(requestvisitcardmessage.getTargetuserid());
if (!targetUser) {
throw 'Could not find user for id '+requestvisitcardmessage.getTargetuserid();
}
const url = await adminApi.fetchVisitCardUrl(targetUser.uuid);
const visitCardMessage = new VisitCardMessage();
visitCardMessage.setUrl(url);
const clientMessage = new ServerToClientMessage();
clientMessage.setVisitcardmessage(visitCardMessage);
user.socket.write(clientMessage);
}
} }
export const socketManager = new SocketManager(); export const socketManager = new SocketManager();

View File

@ -13,6 +13,8 @@
import LoginScene from "./Login/LoginScene.svelte"; import LoginScene from "./Login/LoginScene.svelte";
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore"; import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte"; import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
import VisitCard from "./VisitCard/VisitCard.svelte";
import {requestVisitCardsStore} from "../Stores/GameStore";
import {Game} from "../Phaser/Game/Game"; import {Game} from "../Phaser/Game/Game";
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore"; import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
@ -73,4 +75,7 @@
<HelpCameraSettingsPopup game={ game }></HelpCameraSettingsPopup> <HelpCameraSettingsPopup game={ game }></HelpCameraSettingsPopup>
</div> </div>
{/if} {/if}
{#if $requestVisitCardsStore}
<VisitCard visitCardUrl={$requestVisitCardsStore}></VisitCard>
{/if}
</div> </div>

View File

@ -0,0 +1,64 @@
<script lang="typescript">
import { fly } from 'svelte/transition';
import {requestVisitCardsStore} from "../../Stores/GameStore";
export let visitCardUrl: string;
function closeCard() {
requestVisitCardsStore.set(null);
}
</script>
<style lang="scss">
.visitCard {
pointer-events: all;
margin-left: auto;
margin-right: auto;
width: 515px;
margin-top: 200px;
.defaultCard {
border-radius: 5px;
border: 2px black solid;
background-color: whitesmoke;
width: 500px;
header {
padding: 5px;
}
}
iframe {
border: 0;
width: 515px;
height: 270px;
overflow: hidden;
}
button {
float: right;
}
}
</style>
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}">
{#if visitCardUrl === 'INVALID'}
<div class="defaultCard">
<header>
<h2>Sorry</h2>
<p style="font-style: italic;">This user doesn't have a contact card.</p>
</header>
<main style="padding: 5px; background-color: gray">
<p>Maybe he is offline, or this feature is deactivated.</p>
</main>
</div>
{:else}
<iframe title="visitCardTitle" src={visitCardUrl}></iframe>
{/if}
<div class="buttonContainer">
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
</div>
</section>

View File

@ -30,7 +30,7 @@ import {
EmoteEventMessage, EmoteEventMessage,
EmotePromptMessage, EmotePromptMessage,
SendUserMessage, SendUserMessage,
BanUserMessage BanUserMessage, RequestVisitCardMessage
} from "../Messages/generated/messages_pb" } from "../Messages/generated/messages_pb"
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -50,6 +50,7 @@ import {worldFullMessageStream} from "./WorldFullMessageStream";
import {worldFullWarningStream} from "./WorldFullWarningStream"; import {worldFullWarningStream} from "./WorldFullWarningStream";
import {connectionManager} from "./ConnectionManager"; import {connectionManager} from "./ConnectionManager";
import {emoteEventStream} from "./EmoteEventStream"; import {emoteEventStream} from "./EmoteEventStream";
import {requestVisitCardsStore} from "../Stores/GameStore";
const manualPingDelay = 20000; const manualPingDelay = 20000;
@ -203,6 +204,8 @@ export class RoomConnection implements RoomConnection {
adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage); adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage);
} else if (message.hasWorldfullwarningmessage()) { } else if (message.hasWorldfullwarningmessage()) {
worldFullWarningStream.onMessage(); worldFullWarningStream.onMessage();
} else if (message.hasVisitcardmessage()) {
requestVisitCardsStore.set(message?.getVisitcardmessage()?.getUrl() as unknown as string);
} else if (message.hasRefreshroommessage()) { } else if (message.hasRefreshroommessage()) {
//todo: implement a way to notify the user the room was refreshed. //todo: implement a way to notify the user the room was refreshed.
} else { } else {
@ -617,4 +620,14 @@ export class RoomConnection implements RoomConnection {
this.socket.send(clientToServerMessage.serializeBinary().buffer); 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

@ -3,6 +3,8 @@ import type {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character"; import {Character} from "../Entity/Character";
import type {PlayerAnimationDirections} from "../Player/Animation"; import type {PlayerAnimationDirections} from "../Player/Animation";
export const playerClickedEvent = 'playerClickedEvent';
/** /**
* Class representing the sprite of a remote player (a player that plays on another computer) * Class representing the sprite of a remote player (a player that plays on another computer)
*/ */
@ -25,6 +27,10 @@ export class RemotePlayer extends Character {
//set data //set data
this.userId = userId; this.userId = userId;
this.on('pointerdown', () => {
this.emit(playerClickedEvent, this.userId);
})
} }
updatePosition(position: PointInterface): void { updatePosition(position: PointInterface): void {
@ -40,6 +46,6 @@ export class RemotePlayer extends Character {
} }
isClickable(): boolean { isClickable(): boolean {
return false; //todo: make remote players clickable if they are logged in. return true; //todo: make remote players clickable if they are logged in.
} }
} }

View File

@ -29,7 +29,7 @@ import type {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation"; import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement"; import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer"; import {playerClickedEvent, RemotePlayer} from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript'; import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
@ -1379,6 +1379,9 @@ ${escapedMessage}
addPlayerData.companion, addPlayerData.companion,
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
); );
player.on(playerClickedEvent, (userID:number) => {
this.connection?.requestVisitCardUrl(userID);
})
this.MapPlayers.add(player); this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player); this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position); player.updatePosition(addPlayerData.position);

View File

@ -1,3 +1,5 @@
import { derived, writable, Writable } from "svelte/store"; import { writable } from "svelte/store";
export const userMovingStore = writable(false); export const userMovingStore = writable(false);
export const requestVisitCardsStore = writable<string|null>(null);

View File

@ -75,6 +75,14 @@ message EmoteEventMessage {
string emote = 2; string emote = 2;
} }
message RequestVisitCardMessage {
int32 targetUserId = 1;
}
message VisitCardMessage {
string url = 1;
}
message QueryJitsiJwtMessage { message QueryJitsiJwtMessage {
string jitsiRoom = 1; string jitsiRoom = 1;
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map! string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
@ -94,6 +102,7 @@ message ClientToServerMessage {
ReportPlayerMessage reportPlayerMessage = 11; ReportPlayerMessage reportPlayerMessage = 11;
QueryJitsiJwtMessage queryJitsiJwtMessage = 12; QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
EmotePromptMessage emotePromptMessage = 13; EmotePromptMessage emotePromptMessage = 13;
RequestVisitCardMessage requestVisitCardMessage = 14;
} }
} }
@ -259,6 +268,7 @@ message ServerToClientMessage {
RefreshRoomMessage refreshRoomMessage = 17; RefreshRoomMessage refreshRoomMessage = 17;
WorldConnexionMessage worldConnexionMessage = 18; WorldConnexionMessage worldConnexionMessage = 18;
EmoteEventMessage emoteEventMessage = 19; EmoteEventMessage emoteEventMessage = 19;
VisitCardMessage visitCardMessage = 20;
} }
} }
@ -330,6 +340,7 @@ message PusherToBackMessage {
SendUserMessage sendUserMessage = 12; SendUserMessage sendUserMessage = 12;
BanUserMessage banUserMessage = 13; BanUserMessage banUserMessage = 13;
EmotePromptMessage emotePromptMessage = 14; EmotePromptMessage emotePromptMessage = 14;
RequestVisitCardMessage requestVisitCardMessage = 15;
} }
} }

View File

@ -13,7 +13,12 @@ import {
PlayGlobalMessage, PlayGlobalMessage,
ReportPlayerMessage, ReportPlayerMessage,
EmoteEventMessage, EmoteEventMessage,
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage, EmotePromptMessage QueryJitsiJwtMessage,
SendUserMessage,
ServerToClientMessage,
CompanionMessage,
EmotePromptMessage,
RequestVisitCardMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js" import {TemplatedApp} from "uWebSockets.js"
@ -333,6 +338,9 @@ export class IoSocketController {
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
} else if (message.hasEmotepromptmessage()){ } else if (message.hasEmotepromptmessage()){
socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage); 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 */ /* Ok is false if backpressure was built up, wait for drain */

View File

@ -24,7 +24,13 @@ import {
AdminPusherToBackMessage, AdminPusherToBackMessage,
ServerToAdminClientMessage, ServerToAdminClientMessage,
EmoteEventMessage, EmoteEventMessage,
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage, EmotePromptMessage UserJoinedRoomMessage,
UserLeftRoomMessage,
AdminMessage,
BanMessage,
RefreshRoomMessage,
EmotePromptMessage,
RequestVisitCardMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable"; import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
@ -294,6 +300,7 @@ export class SocketManager implements ZoneEventListener {
throw 'reported socket user not found'; throw 'reported socket user not found';
} }
//TODO report user on admin application //TODO report user on admin application
//todo: move to back because this fail if the reported player is in another pusher.
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2]) await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2])
} catch (e) { } catch (e) {
console.error('An error occurred on "handleReportMessage"'); console.error('An error occurred on "handleReportMessage"');
@ -597,6 +604,13 @@ export class SocketManager implements ZoneEventListener {
client.backConnection.write(pusherToBackMessage); 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(); export const socketManager = new SocketManager();