Emote silent zone (#1342)

* Add an emote when the user is in silent zone

* Update silent icon strategy

* Update strategy for silent zone

 - Add svelte store
 - Show silent zone indication and replace camera

This update permit to hide silent zone when user is in Jitsi discussion

* Fix css silent zone

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>
This commit is contained in:
grégoire parant 2021-09-05 18:36:22 +02:00 committed by GitHub
parent d2b8d7dc04
commit 4f0bb95a38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 241 additions and 165 deletions

View File

@ -1,6 +1,6 @@
<script lang="typescript"> <script lang="typescript">
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore"; import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore"; import {isSilentStore, requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
import monitorImg from "./images/monitor.svg"; import monitorImg from "./images/monitor.svg";
import monitorCloseImg from "./images/monitor-close.svg"; import monitorCloseImg from "./images/monitor-close.svg";
import cinemaImg from "./images/cinema.svg"; import cinemaImg from "./images/cinema.svg";
@ -12,6 +12,7 @@
import {layoutModeStore} from "../Stores/StreamableCollectionStore"; import {layoutModeStore} from "../Stores/StreamableCollectionStore";
import {LayoutMode} from "../WebRtc/LayoutManager"; import {LayoutMode} from "../WebRtc/LayoutManager";
import {peerStore} from "../Stores/PeerStore"; import {peerStore} from "../Stores/PeerStore";
import {onDestroy} from "svelte";
function screenSharingClick(): void { function screenSharingClick(): void {
if ($requestedScreenSharingState === true) { if ($requestedScreenSharingState === true) {
@ -44,6 +45,12 @@
$layoutModeStore = LayoutMode.Presentation; $layoutModeStore = LayoutMode.Presentation;
} }
} }
let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe(value => {
isSilent = value;
});
onDestroy(unsubscribeIsSilent);
</script> </script>
<div> <div>
@ -55,22 +62,22 @@
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode"> <img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode">
{/if} {/if}
</div> </div>
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}> <div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore || isSilent} class:enabled={$requestedScreenSharingState}>
{#if $requestedScreenSharingState} {#if $requestedScreenSharingState && !isSilent}
<img src={monitorImg} alt="Start screen sharing"> <img src={monitorImg} alt="Start screen sharing">
{:else} {:else}
<img src={monitorCloseImg} alt="Stop screen sharing"> <img src={monitorCloseImg} alt="Stop screen sharing">
{/if} {/if}
</div> </div>
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}> <div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
{#if $requestedCameraState} {#if $requestedCameraState && !isSilent}
<img src={cinemaImg} alt="Turn on webcam"> <img src={cinemaImg} alt="Turn on webcam">
{:else} {:else}
<img src={cinemaCloseImg} alt="Turn off webcam"> <img src={cinemaCloseImg} alt="Turn off webcam">
{/if} {/if}
</div> </div>
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}> <div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
{#if $requestedMicrophoneState} {#if $requestedMicrophoneState && !isSilent}
<img src={microphoneImg} alt="Turn on microphone"> <img src={microphoneImg} alt="Turn on microphone">
{:else} {:else}
<img src={microphoneCloseImg} alt="Turn off microphone"> <img src={microphoneCloseImg} alt="Turn off microphone">

View File

@ -1,6 +1,6 @@
<script lang="typescript"> <script lang="typescript">
import {obtainedMediaConstraintStore} from "../Stores/MediaStore"; import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
import {localStreamStore} from "../Stores/MediaStore"; import {localStreamStore, isSilentStore} from "../Stores/MediaStore";
import SoundMeterWidget from "./SoundMeterWidget.svelte"; import SoundMeterWidget from "./SoundMeterWidget.svelte";
import {onDestroy} from "svelte"; import {onDestroy} from "svelte";
import {srcObject} from "./Video/utils"; import {srcObject} from "./Video/utils";
@ -17,14 +17,25 @@
onDestroy(unsubscribe); onDestroy(unsubscribe);
let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe(value => {
isSilent = value;
});
onDestroy(unsubscribeIsSilent);
</script> </script>
<div> <div>
<div class="video-container div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video}> <div class="video-container div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
{#if $localStreamStore.type === "success" && $localStreamStore.stream} {#if $localStreamStore.type === "success" && $localStreamStore.stream}
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video> <video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video>
<SoundMeterWidget stream={stream}></SoundMeterWidget> <SoundMeterWidget stream={stream}></SoundMeterWidget>
{/if} {/if}
</div> </div>
<div class="is-silent" class:hide={isSilent}>
Silent zone
</div>
</div> </div>

View File

@ -1,29 +1,30 @@
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation"; import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
import {SpeechBubble} from "./SpeechBubble"; import { SpeechBubble } from "./SpeechBubble";
import Text = Phaser.GameObjects.Text; import Text = Phaser.GameObjects.Text;
import Container = Phaser.GameObjects.Container; import Container = Phaser.GameObjects.Container;
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError"; import { TextureError } from "../../Exception/TextureError";
import {Companion} from "../Companion/Companion"; import { Companion } from "../Companion/Companion";
import type {GameScene} from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
import {waScaleManager} from "../Services/WaScaleManager"; import { waScaleManager } from "../Services/WaScaleManager";
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
import { isSilentStore } from "../../Stores/MediaStore";
const playerNameY = - 25; const playerNameY = -25;
interface AnimationData { interface AnimationData {
key: string; key: string;
frameRate: number; frameRate: number;
repeat: number; repeat: number;
frameModel: string; //todo use an enum frameModel: string; //todo use an enum
frames : number[] frames: number[];
} }
const interactiveRadius = 35; const interactiveRadius = 35;
export abstract class Character extends Container { export abstract class Character extends Container {
private bubble: SpeechBubble|null = null; private bubble: SpeechBubble | null = null;
private readonly playerName: Text; private readonly playerName: Text;
public PlayerValue: string; public PlayerValue: string;
public sprites: Map<string, Sprite>; public sprites: Map<string, Sprite>;
@ -32,35 +33,41 @@ export abstract class Character extends Container {
private invisible: boolean; private invisible: boolean;
public companion?: Companion; public companion?: Companion;
private emote: Phaser.GameObjects.Sprite | null = null; private emote: Phaser.GameObjects.Sprite | null = null;
private emoteTween: Phaser.Tweens.Tween|null = null; private emoteTween: Phaser.Tweens.Tween | null = null;
scene: GameScene; scene: GameScene;
constructor(scene: GameScene, constructor(
x: number, scene: GameScene,
y: number, x: number,
texturesPromise: Promise<string[]>, y: number,
name: string, texturesPromise: Promise<string[]>,
direction: PlayerAnimationDirections, name: string,
moving: boolean, direction: PlayerAnimationDirections,
frame: string | number, moving: boolean,
isClickable: boolean, frame: string | number,
companion: string|null, isClickable: boolean,
companionTexturePromise?: Promise<string> companion: string | null,
companionTexturePromise?: Promise<string>
) { ) {
super(scene, x, y/*, texture, frame*/); super(scene, x, y /*, texture, frame*/);
this.scene = scene; this.scene = scene;
this.PlayerValue = name; this.PlayerValue = name;
this.invisible = true this.invisible = true;
this.sprites = new Map<string, Sprite>(); this.sprites = new Map<string, Sprite>();
//textures are inside a Promise in case they need to be lazyloaded before use. //textures are inside a Promise in case they need to be lazyloaded before use.
texturesPromise.then((textures) => { texturesPromise.then((textures) => {
this.addTextures(textures, frame); this.addTextures(textures, frame);
this.invisible = false this.invisible = false;
}) });
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"}); this.playerName = new Text(scene, 0, playerNameY, name, {
fontFamily: '"Press Start 2P"',
fontSize: "8px",
strokeThickness: 2,
stroke: "gray",
});
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX); this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName); this.add(this.playerName);
@ -71,18 +78,17 @@ export abstract class Character extends Container {
useHandCursor: true, useHandCursor: true,
}); });
this.on('pointerover',() => { this.on("pointerover", () => {
this.getOutlinePlugin()?.add(this.playerName, { this.getOutlinePlugin()?.add(this.playerName, {
thickness: 2, thickness: 2,
outlineColor: 0xffff00 outlineColor: 0xffff00,
}); });
this.scene.markDirty(); this.scene.markDirty();
}); });
this.on('pointerout',() => { this.on("pointerout", () => {
this.getOutlinePlugin()?.remove(this.playerName); this.getOutlinePlugin()?.remove(this.playerName);
this.scene.markDirty(); this.scene.markDirty();
}) });
} }
scene.add.existing(this); scene.add.existing(this);
@ -97,38 +103,38 @@ export abstract class Character extends Container {
this.playAnimation(direction, moving); this.playAnimation(direction, moving);
if (typeof companion === 'string') { if (typeof companion === "string") {
this.addCompanion(companion, companionTexturePromise); this.addCompanion(companion, companionTexturePromise);
} }
} }
private getOutlinePlugin(): OutlinePipelinePlugin|undefined { private getOutlinePlugin(): OutlinePipelinePlugin | undefined {
return this.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined; return this.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
} }
public addCompanion(name: string, texturePromise?: Promise<string>): void { public addCompanion(name: string, texturePromise?: Promise<string>): void {
if (typeof texturePromise !== 'undefined') { if (typeof texturePromise !== "undefined") {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise); this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
} }
} }
public addTextures(textures: string[], frame?: string | number): void { public addTextures(textures: string[], frame?: string | number): void {
for (const texture of textures) { for (const texture of textures) {
if(this.scene && !this.scene.textures.exists(texture)){ if (this.scene && !this.scene.textures.exists(texture)) {
throw new TextureError('texture not found'); throw new TextureError("texture not found");
} }
const sprite = new Sprite(this.scene, 0, 0, texture, frame); const sprite = new Sprite(this.scene, 0, 0, texture, frame);
this.add(sprite); this.add(sprite);
this.getPlayerAnimations(texture).forEach(d => { this.getPlayerAnimations(texture).forEach((d) => {
this.scene.anims.create({ this.scene.anims.create({
key: d.key, key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {frames: d.frames}), frames: this.scene.anims.generateFrameNumbers(d.frameModel, { frames: d.frames }),
frameRate: d.frameRate, frameRate: d.frameRate,
repeat: d.repeat repeat: d.repeat,
}); });
}) });
// Needed, otherwise, animations are not handled correctly. // Needed, otherwise, animations are not handled correctly.
if(this.scene) { if (this.scene) {
this.scene.sys.updateList.add(sprite); this.scene.sys.updateList.add(sprite);
} }
this.sprites.set(texture, sprite); this.sprites.set(texture, sprite);
@ -136,68 +142,77 @@ export abstract class Character extends Container {
} }
private getPlayerAnimations(name: string): AnimationData[] { private getPlayerAnimations(name: string): AnimationData[] {
return [{ return [
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`, {
frameModel: name, key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frames: [0, 1, 2, 1], frameModel: name,
frameRate: 10, frames: [0, 1, 2, 1],
repeat: -1 frameRate: 10,
}, { repeat: -1,
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`, },
frameModel: name, {
frames: [3, 4, 5, 4], key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frameRate: 10, frameModel: name,
repeat: -1 frames: [3, 4, 5, 4],
}, { frameRate: 10,
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`, repeat: -1,
frameModel: name, },
frames: [6, 7, 8, 7], {
frameRate: 10, key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
repeat: -1 frameModel: name,
}, { frames: [6, 7, 8, 7],
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`, frameRate: 10,
frameModel: name, repeat: -1,
frames: [9, 10, 11, 10], },
frameRate: 10, {
repeat: -1 key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
},{ frameModel: name,
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`, frames: [9, 10, 11, 10],
frameModel: name, frameRate: 10,
frames: [1], repeat: -1,
frameRate: 10, },
repeat: 1 {
}, { key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`, frameModel: name,
frameModel: name, frames: [1],
frames: [4], frameRate: 10,
frameRate: 10, repeat: 1,
repeat: 1 },
}, { {
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`, key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
frameModel: name, frameModel: name,
frames: [7], frames: [4],
frameRate: 10, frameRate: 10,
repeat: 1 repeat: 1,
}, { },
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`, {
frameModel: name, key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
frames: [10], frameModel: name,
frameRate: 10, frames: [7],
repeat: 1 frameRate: 10,
}]; repeat: 1,
},
{
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
frameModel: name,
frames: [10],
frameRate: 10,
repeat: 1,
},
];
} }
protected playAnimation(direction : PlayerAnimationDirections, moving: boolean): void { protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
if (this.invisible) return; if (this.invisible) return;
for (const [texture, sprite] of this.sprites.entries()) { for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) { if (!sprite.anims) {
console.error('ANIMS IS NOT DEFINED!!!'); console.error("ANIMS IS NOT DEFINED!!!");
return; return;
} }
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) { if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true); sprite.play(texture + "-" + direction + "-" + PlayerAnimationTypes.Walk, true);
} else if (!moving) { } else if (!moving) {
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true); sprite.anims.play(texture + "-" + direction + "-" + PlayerAnimationTypes.Idle, true);
} }
} }
} }
@ -205,7 +220,7 @@ export abstract class Character extends Container {
protected getBody(): Phaser.Physics.Arcade.Body { protected getBody(): Phaser.Physics.Arcade.Body {
const body = this.body; const body = this.body;
if (!(body instanceof Phaser.Physics.Arcade.Body)) { if (!(body instanceof Phaser.Physics.Arcade.Body)) {
throw new Error('Container does not have arcade body'); throw new Error("Container does not have arcade body");
} }
return body; return body;
} }
@ -216,16 +231,20 @@ export abstract class Character extends Container {
body.setVelocity(x, y); body.setVelocity(x, y);
// up or down animations are prioritized over left and right // up or down animations are prioritized over left and right
if (body.velocity.y < 0) { //moving up if (body.velocity.y < 0) {
//moving up
this.lastDirection = PlayerAnimationDirections.Up; this.lastDirection = PlayerAnimationDirections.Up;
this.playAnimation(PlayerAnimationDirections.Up, true); this.playAnimation(PlayerAnimationDirections.Up, true);
} else if (body.velocity.y > 0) { //moving down } else if (body.velocity.y > 0) {
//moving down
this.lastDirection = PlayerAnimationDirections.Down; this.lastDirection = PlayerAnimationDirections.Down;
this.playAnimation(PlayerAnimationDirections.Down, true); this.playAnimation(PlayerAnimationDirections.Down, true);
} else if (body.velocity.x > 0) { //moving right } else if (body.velocity.x > 0) {
//moving right
this.lastDirection = PlayerAnimationDirections.Right; this.lastDirection = PlayerAnimationDirections.Right;
this.playAnimation(PlayerAnimationDirections.Right, true); this.playAnimation(PlayerAnimationDirections.Right, true);
} else if (body.velocity.x < 0) { //moving left } else if (body.velocity.x < 0) {
//moving left
this.lastDirection = PlayerAnimationDirections.Left; this.lastDirection = PlayerAnimationDirections.Left;
this.playAnimation(PlayerAnimationDirections.Left, true); this.playAnimation(PlayerAnimationDirections.Left, true);
} }
@ -237,32 +256,39 @@ export abstract class Character extends Container {
} }
} }
stop(){ stop() {
this.getBody().setVelocity(0, 0); this.getBody().setVelocity(0, 0);
this.playAnimation(this.lastDirection, false); this.playAnimation(this.lastDirection, false);
} }
say(text: string) { say(text: string) {
if (this.bubble) return; if (this.bubble) return;
this.bubble = new SpeechBubble(this.scene, this, text) this.bubble = new SpeechBubble(this.scene, this, text);
setTimeout(() => { setTimeout(() => {
if (this.bubble !== null) { if (this.bubble !== null) {
this.bubble.destroy(); this.bubble.destroy();
this.bubble = null; this.bubble = null;
} }
}, 3000) }, 3000);
} }
destroy(): void { destroy(): void {
for (const sprite of this.sprites.values()) { for (const sprite of this.sprites.values()) {
if(this.scene) { if (this.scene) {
this.scene.sys.updateList.remove(sprite); this.scene.sys.updateList.remove(sprite);
} }
} }
this.list.forEach(objectContaining => objectContaining.destroy()) this.list.forEach((objectContaining) => objectContaining.destroy());
super.destroy(); super.destroy();
} }
isSilent() {
isSilentStore.set(true);
}
noSilent() {
isSilentStore.set(false);
}
playEmote(emoteKey: string) { playEmote(emoteKey: string) {
this.cancelPreviousEmote(); this.cancelPreviousEmote();
@ -270,7 +296,7 @@ export abstract class Character extends Container {
const emoteY = -30 - scalingFactor * 10; const emoteY = -30 - scalingFactor * 10;
this.playerName.setVisible(false); this.playerName.setVisible(false);
this.emote = new Sprite(this.scene, 0, 0, emoteKey); this.emote = new Sprite(this.scene, 0, 0, emoteKey);
this.emote.setAlpha(0); this.emote.setAlpha(0);
this.emote.setScale(0.1 * scalingFactor); this.emote.setScale(0.1 * scalingFactor);
this.add(this.emote); this.add(this.emote);
@ -287,11 +313,11 @@ export abstract class Character extends Container {
alpha: 1, alpha: 1,
y: emoteY, y: emoteY,
}, },
ease: 'Power2', ease: "Power2",
duration: 500, duration: 500,
onComplete: () => { onComplete: () => {
this.startPulseTransition(emoteY, scalingFactor); this.startPulseTransition(emoteY, scalingFactor);
} },
}); });
} }
@ -300,7 +326,7 @@ export abstract class Character extends Container {
targets: this.emote, targets: this.emote,
props: { props: {
y: emoteY * 1.3, y: emoteY * 1.3,
scale: scalingFactor * 1.1 scale: scalingFactor * 1.1,
}, },
duration: 250, duration: 250,
yoyo: true, yoyo: true,
@ -308,7 +334,7 @@ export abstract class Character extends Container {
completeDelay: 200, completeDelay: 200,
onComplete: () => { onComplete: () => {
this.startExitTransition(emoteY); this.startExitTransition(emoteY);
} },
}); });
} }
@ -319,11 +345,11 @@ export abstract class Character extends Container {
alpha: 0, alpha: 0,
y: 2 * emoteY, y: 2 * emoteY,
}, },
ease: 'Power2', ease: "Power2",
duration: 500, duration: 500,
onComplete: () => { onComplete: () => {
this.destroyEmote(); this.destroyEmote();
} },
}); });
} }
@ -331,7 +357,7 @@ export abstract class Character extends Container {
if (!this.emote) return; if (!this.emote) return;
this.emoteTween?.remove(); this.emoteTween?.remove();
this.destroyEmote() this.destroyEmote();
} }
private destroyEmote() { private destroyEmote() {

View File

@ -1,37 +1,36 @@
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import {emoteEventStream} from "../../Connexion/EmoteEventStream"; import { emoteEventStream } from "../../Connexion/EmoteEventStream";
import type {GameScene} from "./GameScene"; import type { GameScene } from "./GameScene";
import type {RadialMenuItem} from "../Components/RadialMenu"; import type { RadialMenuItem } from "../Components/RadialMenu";
import LoaderPlugin = Phaser.Loader.LoaderPlugin; import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type {Subscription} from "rxjs"; import type { Subscription } from "rxjs";
interface RegisteredEmote extends BodyResourceDescriptionInterface { interface RegisteredEmote extends BodyResourceDescriptionInterface {
name: string; name: string;
img: string; img: string;
} }
export const emotes: {[key: string]: RegisteredEmote} = { export const emotes: { [key: string]: RegisteredEmote } = {
'emote-heart': {name: 'emote-heart', img: 'resources/emotes/heart-emote.png'}, "emote-heart": { name: "emote-heart", img: "resources/emotes/heart-emote.png" },
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/clap-emote.png'}, "emote-clap": { name: "emote-clap", img: "resources/emotes/clap-emote.png" },
'emote-hand': {name: 'emote-hand', img: 'resources/emotes/hand-emote.png'}, "emote-hand": { name: "emote-hand", img: "resources/emotes/hand-emote.png" },
'emote-thanks': {name: 'emote-thanks', img: 'resources/emotes/thanks-emote.png'}, "emote-thanks": { name: "emote-thanks", img: "resources/emotes/thanks-emote.png" },
'emote-thumb-up': {name: 'emote-thumb-up', img: 'resources/emotes/thumb-up-emote.png'}, "emote-thumb-up": { name: "emote-thumb-up", img: "resources/emotes/thumb-up-emote.png" },
'emote-thumb-down': {name: 'emote-thumb-down', img: 'resources/emotes/thumb-down-emote.png'}, "emote-thumb-down": { name: "emote-thumb-down", img: "resources/emotes/thumb-down-emote.png" },
}; };
export class EmoteManager { export class EmoteManager {
private subscription: Subscription; private subscription: Subscription;
constructor(private scene: GameScene) { constructor(private scene: GameScene) {
this.subscription = emoteEventStream.stream.subscribe((event) => { this.subscription = emoteEventStream.stream.subscribe((event) => {
const actor = this.scene.MapPlayersByKey.get(event.userId); const actor = this.scene.MapPlayersByKey.get(event.userId);
if (actor) { if (actor) {
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => { this.lazyLoadEmoteTexture(event.emoteName).then((emoteKey) => {
actor.playEmote(emoteKey); actor.playEmote(emoteKey);
}) });
} }
}) });
} }
createLoadingPromise(loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) { createLoadingPromise(loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) {
return new Promise<string>((res) => { return new Promise<string>((res) => {
@ -39,20 +38,22 @@ export class EmoteManager {
return res(playerResourceDescriptor.name); return res(playerResourceDescriptor.name);
} }
loadPlugin.image(playerResourceDescriptor.name, playerResourceDescriptor.img); loadPlugin.image(playerResourceDescriptor.name, playerResourceDescriptor.img);
loadPlugin.once('filecomplete-image-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor.name)); loadPlugin.once("filecomplete-image-" + playerResourceDescriptor.name, () =>
res(playerResourceDescriptor.name)
);
}); });
} }
lazyLoadEmoteTexture(textureKey: string): Promise<string> { lazyLoadEmoteTexture(textureKey: string): Promise<string> {
const emoteDescriptor = emotes[textureKey]; const emoteDescriptor = emotes[textureKey];
if (emoteDescriptor === undefined) { if (emoteDescriptor === undefined) {
throw 'Emote not found!'; throw "Emote not found!";
} }
const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor); const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor);
this.scene.load.start(); this.scene.load.start();
return loadPromise return loadPromise;
} }
getMenuImages(): Promise<RadialMenuItem[]> { getMenuImages(): Promise<RadialMenuItem[]> {
const promises = []; const promises = [];
for (const key in emotes) { for (const key in emotes) {
@ -60,14 +61,14 @@ export class EmoteManager {
return { return {
image: textureKey, image: textureKey,
name: textureKey, name: textureKey,
} };
}); });
promises.push(promise); promises.push(promise);
} }
return Promise.all(promises); return Promise.all(promises);
} }
destroy() { destroy() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
} }

View File

@ -93,6 +93,7 @@ import { userIsAdminStore } from "../../Stores/GameStore";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { GameMapPropertiesListener } from "./GameMapPropertiesListener"; import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
import type { RadialMenuItem } from "../Components/RadialMenu";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
@ -853,8 +854,10 @@ export class GameScene extends DirtyScene {
this.gameMap.onPropertyChange("silent", (newValue, oldValue) => { this.gameMap.onPropertyChange("silent", (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === "") { if (newValue === undefined || newValue === false || newValue === "") {
this.connection?.setSilent(false); this.connection?.setSilent(false);
this.CurrentPlayer.noSilent();
} else { } else {
this.connection?.setSilent(true); this.connection?.setSilent(true);
this.CurrentPlayer.isSilent();
} }
}); });
this.gameMap.onPropertyChange("playAudio", (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange("playAudio", (newValue, oldValue, allProps) => {

View File

@ -1,9 +1,9 @@
import {PlayerAnimationDirections} from "./Animation"; import { PlayerAnimationDirections } from "./Animation";
import type {GameScene} from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character"; import { Character } from "../Entity/Character";
import {userMovingStore} from "../../Stores/GameStore"; import { userMovingStore } from "../../Stores/GameStore";
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu"; import { RadialMenu, RadialMenuClickEvent, RadialMenuItem } from "../Components/RadialMenu";
export const hasMovedEventName = "hasMoved"; export const hasMovedEventName = "hasMoved";
export const requestEmoteEventName = "requestEmote"; export const requestEmoteEventName = "requestEmote";
@ -11,7 +11,7 @@ export const requestEmoteEventName = "requestEmote";
export class Player extends Character { export class Player extends Character {
private previousDirection: string = PlayerAnimationDirections.Down; private previousDirection: string = PlayerAnimationDirections.Down;
private wasMoving: boolean = false; private wasMoving: boolean = false;
private emoteMenu: RadialMenu|null = null; private emoteMenu: RadialMenu | null = null;
private updateListener: () => void; private updateListener: () => void;
constructor( constructor(
@ -23,7 +23,7 @@ export class Player extends Character {
direction: PlayerAnimationDirections, direction: PlayerAnimationDirections,
moving: boolean, moving: boolean,
private userInputManager: UserInputManager, private userInputManager: UserInputManager,
companion: string|null, companion: string | null,
companionTexturePromise?: Promise<string> companionTexturePromise?: Promise<string>
) { ) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise); super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
@ -37,7 +37,7 @@ export class Player extends Character {
this.emoteMenu.y = this.y; this.emoteMenu.y = this.y;
} }
}; };
this.scene.events.addListener('postupdate', this.updateListener); this.scene.events.addListener("postupdate", this.updateListener);
} }
moveUser(delta: number): void { moveUser(delta: number): void {
@ -73,14 +73,14 @@ export class Player extends Character {
if (x !== 0 || y !== 0) { if (x !== 0 || y !== 0) {
this.move(x, y); this.move(x, y);
this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y}); this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y });
} else if (this.wasMoving && moving) { } else if (this.wasMoving && moving) {
// slow joystick movement // slow joystick movement
this.move(0, 0); this.move(0, 0);
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y}); this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y });
} else if (this.wasMoving && !moving) { } else if (this.wasMoving && !moving) {
this.stop(); this.stop();
this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y}); this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y });
} }
if (direction !== null) { if (direction !== null) {
@ -94,17 +94,17 @@ export class Player extends Character {
return this.wasMoving; return this.wasMoving;
} }
openOrCloseEmoteMenu(emotes:RadialMenuItem[]) { openOrCloseEmoteMenu(emotes: RadialMenuItem[]) {
if(this.emoteMenu) { if (this.emoteMenu) {
this.closeEmoteMenu(); this.closeEmoteMenu();
} else { } else {
this.openEmoteMenu(emotes); this.openEmoteMenu(emotes);
} }
} }
openEmoteMenu(emotes:RadialMenuItem[]): void { openEmoteMenu(emotes: RadialMenuItem[]): void {
this.cancelPreviousEmote(); this.cancelPreviousEmote();
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes) this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes);
this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => { this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => {
this.closeEmoteMenu(); this.closeEmoteMenu();
this.emit(requestEmoteEventName, item.name); this.emit(requestEmoteEventName, item.name);
@ -112,6 +112,13 @@ export class Player extends Character {
}); });
} }
isSilent() {
super.isSilent();
}
noSilent() {
super.noSilent();
}
closeEmoteMenu(): void { closeEmoteMenu(): void {
if (!this.emoteMenu) return; if (!this.emoteMenu) return;
this.emoteMenu.destroy(); this.emoteMenu.destroy();
@ -119,7 +126,7 @@ export class Player extends Character {
} }
destroy() { destroy() {
this.scene.events.removeListener('postupdate', this.updateListener); this.scene.events.removeListener("postupdate", this.updateListener);
super.destroy(); super.destroy();
} }
} }

View File

@ -569,3 +569,8 @@ localStreamStore.subscribe((streamResult) => {
* A store containing the real active media is mobile * A store containing the real active media is mobile
*/ */
export const obtainedMediaConstraintIsMobileStore = writable(false); export const obtainedMediaConstraintIsMobileStore = writable(false);
/**
* 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);

View File

@ -1083,3 +1083,19 @@ div.action.danger p.action-body{
} }
} }
} }
div.is-silent {
position: absolute;
bottom: 40px;
border-radius: 15px 15px 15px 15px;
max-height: 20%;
transition: right 350ms;
right: -20vw;
background-color: black;
font-size: 20px;
color: white;
padding: 30px 20px;
}
div.is-silent.hide {
right: 15px;
}