Merge pull request #2091 from thecodingmachine/add-away-indicator-for-hero

Availability Statuses
This commit is contained in:
David Négrier
2022-04-22 15:19:48 +02:00
committed by GitHub
28 changed files with 528 additions and 183 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
import { requestedCameraState, requestedMicrophoneState, silentStore } from "../../Stores/MediaStore";
import { get } from "svelte/store";
import { WorkAdventureDesktopApi } from "@wa-preload-app";
@@ -36,8 +36,8 @@ class DesktopApi {
}
});
isSilentStore.subscribe((value) => {
this.isSilent = value;
silentStore.subscribe((silent) => {
this.isSilent = silent;
});
}
}
+12 -19
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { requestedScreenSharingState, screenSharingAvailableStore } from "../Stores/ScreenSharingStore";
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
import { requestedCameraState, requestedMicrophoneState, silentStore } from "../Stores/MediaStore";
import monitorImg from "./images/monitor.svg";
import monitorCloseImg from "./images/monitor-close.svg";
import cinemaImg from "./images/cinema.svg";
@@ -13,7 +13,6 @@
import lockImg from "./images/lock.svg";
import { LayoutMode } from "../WebRtc/LayoutManager";
import { peerStore } from "../Stores/PeerStore";
import { onDestroy } from "svelte";
import { embedScreenLayout } from "../Stores/EmbedScreensStore";
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
import { gameManager } from "../Phaser/Game/GameManager";
@@ -22,7 +21,7 @@
const gameScene = gameManager.getCurrentGameScene();
function screenSharingClick(): void {
if (isSilent) return;
if ($silentStore) return;
if ($requestedScreenSharingState === true) {
requestedScreenSharingState.disableScreenSharing();
} else {
@@ -31,7 +30,7 @@
}
function cameraClick(): void {
if (isSilent) return;
if ($silentStore) return;
if ($requestedCameraState === true) {
requestedCameraState.disableWebcam();
} else {
@@ -40,7 +39,7 @@
}
function microphoneClick(): void {
if (isSilent) return;
if ($silentStore) return;
if ($requestedMicrophoneState === true) {
requestedMicrophoneState.disableMicrophone();
} else {
@@ -75,12 +74,6 @@
function lockClick() {
gameScene.connection?.emitLockGroup(!$currentPlayerGroupLockStateStore);
}
let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
isSilent = value;
});
onDestroy(unsubscribeIsSilent);
</script>
<div class="btn-cam-action">
@@ -94,7 +87,7 @@
<div
class="btn-follow"
class:hide={($peerStore.size === 0 && $followStateStore === "off") || isSilent}
class:hide={($peerStore.size === 0 && $followStateStore === "off") || $silentStore}
class:disabled={$followStateStore !== "off"}
on:click={followClick}
>
@@ -103,7 +96,7 @@
<div
class="btn-lock"
class:hide={$peerStore.size === 0 || isSilent}
class:hide={$peerStore.size === 0 || $silentStore}
class:disabled={$currentPlayerGroupLockStateStore}
on:click={lockClick}
>
@@ -113,26 +106,26 @@
<div
class="btn-monitor"
on:click={screenSharingClick}
class:hide={!$screenSharingAvailableStore || isSilent}
class:hide={!$screenSharingAvailableStore || $silentStore}
class:enabled={$requestedScreenSharingState}
>
{#if $requestedScreenSharingState && !isSilent}
{#if $requestedScreenSharingState && !$silentStore}
<img class="noselect" src={monitorImg} alt="Start screen sharing" />
{:else}
<img class="noselect" src={monitorCloseImg} alt="Stop screen sharing" />
{/if}
</div>
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
{#if $requestedCameraState && !isSilent}
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState || $silentStore}>
{#if $requestedCameraState && !$silentStore}
<img class="noselect" src={cinemaImg} alt="Turn on webcam" />
{:else}
<img class="noselect" src={cinemaCloseImg} alt="Turn off webcam" />
{/if}
</div>
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
{#if $requestedMicrophoneState && !isSilent}
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || $silentStore}>
{#if $requestedMicrophoneState && !$silentStore}
<img class="noselect" src={microphoneImg} alt="Turn on microphone" />
{:else}
<img class="noselect" src={microphoneCloseImg} alt="Turn off microphone" />
+4 -11
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { localVolumeStore, obtainedMediaConstraintStore } from "../Stores/MediaStore";
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
import { localVolumeStore, obtainedMediaConstraintStore, silentStore } from "../Stores/MediaStore";
import { localStreamStore } from "../Stores/MediaStore";
import SoundMeterWidget from "./SoundMeterWidget.svelte";
import { onDestroy, onMount } from "svelte";
import { srcObject } from "./Video/utils";
@@ -20,11 +20,6 @@
unsubscribeLocalStreamStore();
});
let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
isSilent = value;
});
let cameraContainer: HTMLDivElement;
onMount(() => {
@@ -40,16 +35,14 @@
}
});
});
onDestroy(unsubscribeIsSilent);
</script>
<div
class="nes-container is-rounded my-cam-video-container"
class:hide={($localStreamStore.type !== "success" || !$obtainedMediaConstraintStore.video) && !isSilent}
class:hide={($localStreamStore.type !== "success" || !$obtainedMediaConstraintStore.video) && !$silentStore}
bind:this={cameraContainer}
>
{#if isSilent}
{#if $silentStore}
<div class="is-silent">{$LL.camera.my.silentZone()}</div>
{:else if $localStreamStore.type === "success" && $localStreamStore.stream}
<video class="my-cam-video" use:srcObject={stream} autoplay muted playsinline />
+3 -2
View File
@@ -1,6 +1,7 @@
import type { SignalData } from "simple-peer";
import type { RoomConnection } from "./RoomConnection";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import { AvailabilityStatus } from "../Messages/ts-proto-generated/protos/messages";
export interface PointInterface {
x: number;
@@ -14,7 +15,7 @@ export interface MessageUserPositionInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
away: boolean;
status: AvailabilityStatus;
visitCardUrl: string | null;
companion: string | null;
userUuid: string;
@@ -30,7 +31,7 @@ export interface MessageUserJoined {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
away: boolean;
status: AvailabilityStatus;
visitCardUrl: string | null;
companion: string | null;
userUuid: string;
+4 -16
View File
@@ -41,6 +41,7 @@ import {
SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto,
PingMessage as PingMessageTsProto,
CharacterLayerMessage,
AvailabilityStatus,
} from "../Messages/ts-proto-generated/protos/messages";
import { Subject } from "rxjs";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
@@ -520,9 +521,9 @@ export class RoomConnection implements RoomConnection {
this.socket.send(bytes);
}
public emitPlayerAway(away: boolean): void {
public emitPlayerStatusChange(status: AvailabilityStatus): void {
const message = SetPlayerDetailsMessageTsProto.fromPartial({
away,
status,
});
const bytes = ClientToServerMessageTsProto.encode({
message: {
@@ -614,19 +615,6 @@ export class RoomConnection implements RoomConnection {
this.socket.send(bytes);
}
public setSilent(silent: boolean): void {
const bytes = ClientToServerMessageTsProto.encode({
message: {
$case: "silentMessage",
silentMessage: {
silent,
},
},
}).finish();
this.socket.send(bytes);
}
public setViewport(viewport: ViewportInterface): void {
const bytes = ClientToServerMessageTsProto.encode({
message: {
@@ -670,7 +658,7 @@ export class RoomConnection implements RoomConnection {
characterLayers,
visitCardUrl: message.visitCardUrl,
position: ProtobufClientUtils.toPointInterface(position),
away: message.away,
status: message.status,
companion: companion ? companion.name : null,
userUuid: message.userUuid,
outlineColor: message.hasOutline ? message.outlineColor : undefined,
+23 -20
View File
@@ -1,36 +1,41 @@
import { AvailabilityStatus } from "../../Messages/ts-proto-generated/protos/messages";
import { Easing } from "../../types";
export class PlayerStatusDot extends Phaser.GameObjects.Container {
private graphics: Phaser.GameObjects.Graphics;
private statusImage: Phaser.GameObjects.Image;
private statusImageOutline: Phaser.GameObjects.Image;
private away: boolean;
private status: AvailabilityStatus;
private readonly COLORS = {
// online: 0x00ff00,
// away: 0xffff00,
online: 0x8cc43f,
onlineOutline: 0x427a25,
away: 0xf5931e,
awayOutline: 0x875d13,
private readonly COLORS: Record<AvailabilityStatus, { filling: number; outline: number }> = {
[AvailabilityStatus.AWAY]: { filling: 0xf5931e, outline: 0x875d13 },
[AvailabilityStatus.ONLINE]: { filling: 0x8cc43f, outline: 0x427a25 },
[AvailabilityStatus.SILENT]: { filling: 0xe74c3c, outline: 0xc0392b },
[AvailabilityStatus.JITSI]: { filling: 0x8cc43f, outline: 0x427a25 },
[AvailabilityStatus.UNRECOGNIZED]: { filling: 0xffffff, outline: 0xffffff },
[AvailabilityStatus.UNCHANGED]: { filling: 0xffffff, outline: 0xffffff },
};
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y);
this.away = false;
this.status = AvailabilityStatus.ONLINE;
this.statusImage = this.scene.add.image(0, 0, "iconStatusIndicatorInside");
this.statusImageOutline = this.scene.add.image(0, 0, "iconStatusIndicatorOutline");
this.add([this.statusImage, this.statusImageOutline]);
this.graphics = this.scene.add.graphics();
this.add(this.graphics);
this.redraw();
this.scene.add.existing(this);
}
public setAway(away: boolean = true, instant: boolean = false): void {
if (this.away === away) {
public setStatus(status: AvailabilityStatus, instant: boolean = false): void {
if (this.status === status || status === AvailabilityStatus.UNCHANGED) {
return;
}
this.away = away;
this.status = status;
if (instant) {
this.redraw();
} else {
@@ -56,10 +61,8 @@ export class PlayerStatusDot extends Phaser.GameObjects.Container {
}
private redraw(): void {
this.graphics.clear();
this.graphics.fillStyle(this.away ? this.COLORS.away : this.COLORS.online);
this.graphics.lineStyle(1, this.away ? this.COLORS.awayOutline : this.COLORS.onlineOutline);
this.graphics.fillCircle(0, 0, 3);
this.graphics.strokeCircle(0, 0, 3);
const colors = this.COLORS[this.status];
this.statusImage.setTintFill(colors.filling);
this.statusImageOutline.setTintFill(colors.outline);
}
}
+3 -10
View File
@@ -9,7 +9,6 @@ import { Companion } from "../Companion/Companion";
import type { GameScene } from "../Game/GameScene";
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
import { isSilentStore } from "../../Stores/MediaStore";
import { lazyLoadPlayerCharacterTextures } from "./PlayerTexturesLoadingManager";
import { TexturesHelper } from "../Helpers/TexturesHelper";
import type { PictureStore } from "../../Stores/PictureStore";
@@ -20,6 +19,7 @@ import type CancelablePromise from "cancelable-promise";
import { TalkIcon } from "../Components/TalkIcon";
import { Deferred } from "ts-deferred";
import { PlayerStatusDot } from "../Components/PlayerStatusDot";
import { AvailabilityStatus } from "../../Messages/ts-proto-generated/protos/messages";
const playerNameY = -25;
const interactiveRadius = 35;
@@ -236,8 +236,8 @@ export abstract class Character extends Container implements OutlineableInterfac
this.talkIcon.show(show, forceClose);
}
public setAwayStatus(away: boolean = true, instant: boolean = false): void {
this.statusDot.setAway(away, instant);
public setStatus(status: AvailabilityStatus, instant: boolean = false): void {
this.statusDot.setStatus(status, instant);
}
public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void {
@@ -355,13 +355,6 @@ export abstract class Character extends Container implements OutlineableInterfac
super.destroy();
}
isSilent() {
isSilentStore.set(true);
}
noSilent() {
isSilentStore.set(false);
}
playEmote(emote: string) {
this.cancelPreviousEmote();
const emoteY = -45;
@@ -17,6 +17,7 @@ import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores
import { iframeListener } from "../../Api/IframeListener";
import { Room } from "../../Connexion/Room";
import LL from "../../i18n/i18n-svelte";
import { inJitsiStore, silentStore } from "../../Stores/MediaStore";
interface OpenCoWebsite {
actionId: string;
@@ -77,6 +78,7 @@ export class GameMapPropertiesListener {
coWebsiteManager.closeCoWebsite(coWebsite);
}
});
inJitsiStore.set(false);
} else {
const openJitsiRoomFunction = () => {
let addPrefix = true;
@@ -119,11 +121,15 @@ export class GameMapPropertiesListener {
uuid: "jitsi",
type: "message",
message: message,
callback: () => openJitsiRoomFunction(),
callback: () => {
openJitsiRoomFunction();
inJitsiStore.set(true);
},
userInputManager: this.scene.userInputManager,
});
} else {
openJitsiRoomFunction();
inJitsiStore.set(true);
}
}
});
@@ -160,11 +166,9 @@ export class GameMapPropertiesListener {
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue) => {
if (newValue === undefined || newValue === false || newValue === "") {
this.scene.connection?.setSilent(false);
this.scene.CurrentPlayer.noSilent();
silentStore.set(false);
} else {
this.scene.connection?.setSilent(true);
this.scene.CurrentPlayer.isSilent();
silentStore.set(true);
}
});
+13 -14
View File
@@ -91,7 +91,7 @@ import { MapStore } from "../../Stores/Utils/MapStore";
import { followUsersColorStore } from "../../Stores/FollowStore";
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte";
import { localVolumeStore } from "../../Stores/MediaStore";
import { availabilityStatusStore, localVolumeStore } from "../../Stores/MediaStore";
import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
@@ -101,7 +101,6 @@ import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import { PlayerDetailsUpdatedMessage } from "../../Messages/ts-proto-generated/protos/messages";
import { privacyShutdownStore } from "../../Stores/PrivacyShutdownStore";
export interface GameSceneInitInterface {
initPosition: PointInterface | null;
reconnecting: boolean;
@@ -249,6 +248,8 @@ export class GameScene extends DirtyScene {
this.listenToIframeEvents();
this.load.image("iconTalk", "/resources/icons/icon_talking.png");
this.load.image("iconStatusIndicatorInside", "/resources/icons/icon_status_indicator_inside.png");
this.load.image("iconStatusIndicatorOutline", "/resources/icons/icon_status_indicator_outline.png");
if (touchScreenManager.supportTouchScreen) {
this.load.image(joystickBaseKey, joystickBaseImg);
@@ -679,6 +680,11 @@ export class GameScene extends DirtyScene {
this.tryChangeShowVoiceIndicatorState(this.jitsiDominantSpeaker && this.jitsiParticipantsCount > 1);
});
availabilityStatusStore.subscribe((status) => {
this.connection?.emitPlayerStatusChange(status);
this.CurrentPlayer.setStatus(status);
});
this.emoteUnsubscribe = emoteStore.subscribe((emote) => {
if (emote) {
this.CurrentPlayer?.playEmote(emote.url);
@@ -705,10 +711,6 @@ export class GameScene extends DirtyScene {
}
});
this.privacyShutdownStoreUnsubscribe = privacyShutdownStore.subscribe((away) => {
this.connection?.emitPlayerAway(away);
});
Promise.all([
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
...scriptPromises,
@@ -767,7 +769,7 @@ export class GameScene extends DirtyScene {
characterLayers: message.characterLayers,
name: message.name,
position: message.position,
away: message.away,
status: message.status,
visitCardUrl: message.visitCardUrl,
companion: message.companion,
userUuid: message.userUuid,
@@ -1933,8 +1935,8 @@ ${escapedMessage}
if (addPlayerData.outlineColor !== undefined) {
player.setApiOutlineColor(addPlayerData.outlineColor);
}
if (addPlayerData.away !== undefined) {
player.setAwayStatus(addPlayerData.away, true);
if (addPlayerData.status !== undefined) {
player.setStatus(addPlayerData.status, true);
}
this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player);
@@ -2085,8 +2087,8 @@ ${escapedMessage}
if (message.details?.showVoiceIndicator !== undefined) {
character.showTalkIcon(message.details?.showVoiceIndicator);
}
if (message.details?.away !== undefined) {
character.setAwayStatus(message.details?.away);
if (message.details?.status !== undefined) {
character.setStatus(message.details?.status);
}
}
@@ -2130,13 +2132,10 @@ ${escapedMessage}
}
public enableMediaBehaviors() {
const silent = this.gameMap.getCurrentProperties().get(GameMapProperties.SILENT);
this.connection?.setSilent(!!silent);
mediaManager.showMyCamera();
}
public disableMediaBehaviors() {
this.connection?.setSilent(true);
mediaManager.hideMyCamera();
}
+2 -1
View File
@@ -1,3 +1,4 @@
import { AvailabilityStatus } from "../../Messages/ts-proto-generated/protos/messages";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
export interface PlayerInterface {
@@ -7,7 +8,7 @@ export interface PlayerInterface {
visitCardUrl: string | null;
companion: string | null;
userUuid: string;
away: boolean;
status: AvailabilityStatus;
color?: string;
outlineColor?: number;
}
-1
View File
@@ -34,7 +34,6 @@ export class PlayerMovement {
const y =
(this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) +
this.startPosition.y;
//console.log('Computed position ', x, y)
return {
x,
y,
@@ -67,4 +67,20 @@ export class TexturesHelper {
rectangleTexture.generateTexture(textureKey, width, height);
rectangleTexture.destroy();
}
public static createCircleTexture(
scene: Phaser.Scene,
textureKey: string,
radius: number,
color: number,
outlineColor?: number,
outlineThickness?: number
): void {
const circleTexture = scene.add.graphics().fillStyle(color, 1).fillCircle(radius, radius, radius);
if (outlineColor) {
circleTexture.lineStyle(outlineThickness ?? 1, outlineColor).strokeCircle(radius, radius, radius);
}
circleTexture.generateTexture(textureKey, radius * 2, radius * 2);
circleTexture.destroy();
}
}
-1
View File
@@ -28,7 +28,6 @@ export class Player extends Character {
companionTexturePromise?: CancelablePromise<string>
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
this.statusDot.setVisible(false);
//the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false);
}
+17 -7
View File
@@ -11,6 +11,7 @@ import { peerStore } from "./PeerStore";
import { privacyShutdownStore } from "./PrivacyShutdownStore";
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { AvailabilityStatus } from "../Messages/ts-proto-generated/protos/messages";
import deepEqual from "fast-deep-equal";
/**
@@ -179,10 +180,19 @@ function createVideoConstraintStore() {
};
}
/**
* A store containing if user is silent, so if he is in silent zone. This permit to show et hide camera of user
*/
export const isSilentStore = writable(false);
export const inJitsiStore = writable(false);
export const silentStore = writable(false);
export const availabilityStatusStore = derived(
[inJitsiStore, silentStore, privacyShutdownStore],
([$inJitsiStore, $silentStore, $privacyShutdownStore]) => {
if ($inJitsiStore) return AvailabilityStatus.JITSI;
if ($silentStore) return AvailabilityStatus.SILENT;
if ($privacyShutdownStore) return AvailabilityStatus.AWAY;
return AvailabilityStatus.ONLINE;
},
AvailabilityStatus.ONLINE
);
export const videoConstraintStore = createVideoConstraintStore();
@@ -241,7 +251,7 @@ export const mediaStreamConstraintsStore = derived(
audioConstraintStore,
privacyShutdownStore,
cameraEnergySavingStore,
isSilentStore,
availabilityStatusStore,
],
(
[
@@ -253,7 +263,7 @@ export const mediaStreamConstraintsStore = derived(
$audioConstraintStore,
$privacyShutdownStore,
$cameraEnergySavingStore,
$isSilentStore,
$availabilityStatusStore,
],
set
) => {
@@ -310,7 +320,7 @@ export const mediaStreamConstraintsStore = derived(
//currentAudioConstraint = false;
}
if ($isSilentStore === true) {
if ($availabilityStatusStore === AvailabilityStatus.SILENT) {
currentVideoConstraint = false;
currentAudioConstraint = false;
}
+3 -2
View File
@@ -2,6 +2,7 @@ import { writable } from "svelte/store";
import type { PlayerInterface } from "../Phaser/Game/PlayerInterface";
import type { RoomConnection } from "../Connexion/RoomConnection";
import { getRandomColor } from "../WebRtc/ColorGenerator";
import { AvailabilityStatus } from "../Messages/ts-proto-generated/protos/messages";
let idCount = 0;
@@ -28,7 +29,7 @@ function createPlayersStore() {
visitCardUrl: message.visitCardUrl,
companion: message.companion,
userUuid: message.userUuid,
away: message.away,
status: message.status,
color: getRandomColor(),
});
return users;
@@ -58,7 +59,7 @@ function createPlayersStore() {
characterLayers: [],
visitCardUrl: null,
companion: null,
away: false,
status: AvailabilityStatus.ONLINE,
userUuid: "dummy",
color: getRandomColor(),
});