Merge branch 'develop' into changeRegisterAccess
Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> # Conflicts: # front/src/Connexion/ConnectionManager.ts # front/vite.config.ts
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
||||
import { get } from "svelte/store";
|
||||
import { WorkAdventureDesktopApi } from "@wa-preload-app";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
WAD: WorkAdventureDesktopApi;
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopApi {
|
||||
isSilent: boolean = false;
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
height: max-content !important;
|
||||
max-height: 40vh;
|
||||
margin-top: 200px;
|
||||
z-index: 425;
|
||||
|
||||
pointer-events: auto;
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
let unsubscriberFileStore: Unsubscriber | null = null;
|
||||
let unsubscriberVolumeStore: Unsubscriber | null = null;
|
||||
|
||||
let decreaseWhileTalking: boolean = true;
|
||||
let isAudioAllowed: boolean = true;
|
||||
|
||||
onMount(() => {
|
||||
let volume = Math.min(localUserStore.getAudioPlayerVolume(), get(audioManagerVolumeStore).volume);
|
||||
audioManagerVolumeStore.setVolume(volume);
|
||||
audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted());
|
||||
|
||||
unsubscriberFileStore = audioManagerFileStore.subscribe((src) => {
|
||||
unsubscriberFileStore = audioManagerFileStore.subscribe((src: string) => {
|
||||
HTMLAudioPlayer.pause();
|
||||
HTMLAudioPlayer.src = src;
|
||||
HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop;
|
||||
HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume;
|
||||
HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted;
|
||||
void HTMLAudioPlayer.play();
|
||||
tryPlay();
|
||||
});
|
||||
unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => {
|
||||
const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking;
|
||||
@@ -52,6 +52,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
function tryPlay() {
|
||||
void HTMLAudioPlayer.play()
|
||||
.then(() => {
|
||||
isAudioAllowed = true;
|
||||
})
|
||||
.catch(() => {
|
||||
isAudioAllowed = false;
|
||||
});
|
||||
}
|
||||
|
||||
function updateVolumeUI() {
|
||||
if (get(audioManagerVolumeStore).muted) {
|
||||
audioPlayerVolumeIcon.classList.add("muted");
|
||||
@@ -90,73 +100,67 @@
|
||||
audioPlayerVol.blur();
|
||||
return false;
|
||||
}
|
||||
|
||||
function setDecrease() {
|
||||
audioManagerVolumeStore.setDecreaseWhileTalking(decreaseWhileTalking);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="main-audio-manager nes-container is-rounded">
|
||||
<div class="audio-manager-player-volume">
|
||||
<span
|
||||
id="audioplayer_volume_icon_playing"
|
||||
alt="player volume"
|
||||
bind:this={audioPlayerVolumeIcon}
|
||||
on:click={onMute}
|
||||
>
|
||||
<svg
|
||||
width="2em"
|
||||
height="2em"
|
||||
viewBox="0 0 16 16"
|
||||
class="bi bi-volume-up"
|
||||
fill="white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
|
||||
/>
|
||||
<g id="audioplayer_volume_icon_playing_high">
|
||||
<div class:hidden={!isAudioAllowed}>
|
||||
<div class="audio-manager-player-volume">
|
||||
<span id="audioplayer_volume_icon_playing" bind:this={audioPlayerVolumeIcon} on:click={onMute}>
|
||||
<svg
|
||||
width="2em"
|
||||
height="2em"
|
||||
viewBox="0 0 16 16"
|
||||
class="bi bi-volume-up"
|
||||
fill="white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
|
||||
fill-rule="evenodd"
|
||||
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
|
||||
/>
|
||||
</g>
|
||||
<g id="audioplayer_volume_icon_playing_mid">
|
||||
<path
|
||||
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
|
||||
/>
|
||||
</g>
|
||||
<g id="audioplayer_volume_icon_playing_low">
|
||||
<path
|
||||
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.025"
|
||||
bind:this={audioPlayerVol}
|
||||
on:change={setVolume}
|
||||
on:keydown={disallowKeys}
|
||||
/>
|
||||
</div>
|
||||
<div class="audio-manager-reduce-conversation">
|
||||
<label>
|
||||
{$LL.audio.manager.reduce()}
|
||||
<input type="checkbox" bind:checked={decreaseWhileTalking} on:change={setDecrease} />
|
||||
</label>
|
||||
<g id="audioplayer_volume_icon_playing_high">
|
||||
<path
|
||||
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
|
||||
/>
|
||||
</g>
|
||||
<g id="audioplayer_volume_icon_playing_mid">
|
||||
<path
|
||||
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
|
||||
/>
|
||||
</g>
|
||||
<g id="audioplayer_volume_icon_playing_low">
|
||||
<path
|
||||
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.025"
|
||||
bind:this={audioPlayerVol}
|
||||
on:change={setVolume}
|
||||
on:keydown={disallowKeys}
|
||||
/>
|
||||
</div>
|
||||
<section class="audio-manager-file">
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<audio class="audio-manager-audioplayer" bind:this={HTMLAudioPlayer} />
|
||||
</section>
|
||||
</div>
|
||||
<div class:hidden={isAudioAllowed}>
|
||||
<button type="button" class="nes-btn" on:click={tryPlay}>{$LL.audio.manager.allow()}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.main-audio-manager.nes-container.is-rounded {
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.main-audio-manager {
|
||||
position: absolute;
|
||||
top: 1%;
|
||||
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
import layoutPresentationImg from "./images/layout-presentation.svg";
|
||||
import layoutChatImg from "./images/layout-chat.svg";
|
||||
import followImg from "./images/follow.svg";
|
||||
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";
|
||||
import { currentPlayerGroupLockStateStore } from "../Stores/CurrentPlayerGroupStore";
|
||||
|
||||
const gameScene = gameManager.getCurrentGameScene();
|
||||
|
||||
@@ -70,6 +72,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function lockClick() {
|
||||
gameScene.connection?.emitLockGroup(!$currentPlayerGroupLockStateStore);
|
||||
}
|
||||
|
||||
let isSilent: boolean;
|
||||
const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
|
||||
isSilent = value;
|
||||
@@ -95,6 +101,15 @@
|
||||
<img class="noselect" src={followImg} alt="" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="btn-lock"
|
||||
class:hide={$peerStore.size === 0 || isSilent}
|
||||
class:disabled={$currentPlayerGroupLockStateStore}
|
||||
on:click={lockClick}
|
||||
>
|
||||
<img class="noselect" src={lockImg} alt="" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="btn-monitor"
|
||||
on:click={screenSharingClick}
|
||||
@@ -162,7 +177,7 @@
|
||||
transform: translateY(15px);
|
||||
transition-timing-function: ease-in-out;
|
||||
transition: all 0.3s;
|
||||
margin: 0 4%;
|
||||
margin: 0 2%;
|
||||
|
||||
&.hide {
|
||||
transform: translateY(60px);
|
||||
@@ -211,6 +226,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-lock {
|
||||
pointer-events: auto;
|
||||
|
||||
img {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
/**
|
||||
* If we cannot hover over elements, let's display camera button in full.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
|
||||
import uploadFile from "../images/jitsi.png";
|
||||
|
||||
export let index: number;
|
||||
export let coWebsite: CoWebsite;
|
||||
export let vertical: boolean;
|
||||
@@ -21,7 +23,7 @@
|
||||
|
||||
onMount(() => {
|
||||
icon.src = isJitsi
|
||||
? "/resources/logos/jitsi.png"
|
||||
? uploadFile
|
||||
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
|
||||
icon.alt = coWebsite.getUrl().hostname;
|
||||
icon.onload = () => {
|
||||
@@ -350,9 +352,14 @@
|
||||
color: white;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,5 +18,7 @@
|
||||
display: flex;
|
||||
padding-top: 2%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 200;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
||||
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
||||
import { getNavigatorType, isAndroid as isAndroidFct, NavigatorType } from "../../WebRtc/DeviceUtils";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
|
||||
@@ -33,9 +31,9 @@
|
||||
<p class="err">
|
||||
{$LL.camera.help.firefoxContent()}
|
||||
</p>
|
||||
<img src={firefoxImg} alt="" />
|
||||
<img src={$LL.camera.help.screen.firefox()} alt="" />
|
||||
{:else if isChrome && !isAndroid}
|
||||
<img src={chromeImg} alt="" />
|
||||
<img src={$LL.camera.help.screen.chrome()} alt="" />
|
||||
{/if}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
@@ -3,6 +3,7 @@
|
||||
import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene";
|
||||
import { DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
|
||||
import logoImg from "../images/logo.png";
|
||||
import poweredByWorkAdventureImg from "../images/Powered_By_WorkAdventure_Big.png";
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
|
||||
@@ -13,6 +14,8 @@
|
||||
let name = gameManager.getPlayerName() || "";
|
||||
let startValidating = false;
|
||||
|
||||
let logo = gameManager.currentStartedRoom.loginSceneLogo ?? logoImg;
|
||||
|
||||
function submit() {
|
||||
startValidating = true;
|
||||
|
||||
@@ -25,7 +28,7 @@
|
||||
|
||||
<form class="loginScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<img src={logoImg} alt="WorkAdventure logo" />
|
||||
<img src={logo} alt="" />
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h2>{$LL.login.input.name.placeholder()}</h2>
|
||||
@@ -60,6 +63,11 @@
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">{$LL.login.continue()}</button>
|
||||
</section>
|
||||
{#if logo !== logoImg}
|
||||
<section class="text-center powered-by">
|
||||
<img src={poweredByWorkAdventureImg} alt="Powered by WorkAdventure" />
|
||||
</section>
|
||||
{/if}
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -132,6 +140,11 @@
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
&.powered-by {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Components ordered by z-index -->
|
||||
<div id="main-layout" bind:this={mainLayout}>
|
||||
<aside id="main-layout-left-aside">
|
||||
{#if $menuIconVisiblilityStore}
|
||||
@@ -104,14 +105,14 @@
|
||||
<ShareLinkMapModal />
|
||||
{/if}
|
||||
|
||||
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||
<FollowMenu />
|
||||
{/if}
|
||||
|
||||
{#if $actionsMenuStore}
|
||||
<ActionsMenu />
|
||||
{/if}
|
||||
|
||||
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||
<FollowMenu />
|
||||
{/if}
|
||||
|
||||
{#if $requestVisitCardsStore}
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||
{/if}
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
|
||||
async function logOut() {
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
return connectionManager.logout();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,24 @@
|
||||
import type { Locales } from "../../i18n/i18n-types";
|
||||
import { displayableLocales, setCurrentLocale } from "../../i18n/locales";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
import { audioManagerVolumeStore } from "../../Stores/AudioManagerStore";
|
||||
|
||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||
let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger();
|
||||
let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests();
|
||||
let decreaseAudioPlayerVolumeWhileTalking: boolean = localUserStore.getDecreaseAudioPlayerVolumeWhileTalking();
|
||||
let valueGame: number = localUserStore.getGameQualityValue();
|
||||
let valueVideo: number = localUserStore.getVideoQualityValue();
|
||||
let valueLocale: string = $locale;
|
||||
let valueCameraPrivacySettings = localUserStore.getCameraPrivacySettings();
|
||||
let valueMicrophonePrivacySettings = localUserStore.getMicrophonePrivacySettings();
|
||||
|
||||
let previewValueGame = valueGame;
|
||||
let previewValueVideo = valueVideo;
|
||||
let previewValueLocale = valueLocale;
|
||||
let previewCameraPrivacySettings = valueCameraPrivacySettings;
|
||||
let previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
|
||||
|
||||
function saveSetting() {
|
||||
let change = false;
|
||||
@@ -38,6 +45,18 @@
|
||||
change = true;
|
||||
}
|
||||
|
||||
if (valueCameraPrivacySettings !== previewCameraPrivacySettings) {
|
||||
previewCameraPrivacySettings = valueCameraPrivacySettings;
|
||||
localUserStore.setCameraPrivacySettings(valueCameraPrivacySettings);
|
||||
}
|
||||
|
||||
if (valueMicrophonePrivacySettings !== previewMicrophonePrivacySettings) {
|
||||
previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
|
||||
localUserStore.setMicrophonePrivacySettings(valueMicrophonePrivacySettings);
|
||||
}
|
||||
|
||||
audioManagerVolumeStore.setDecreaseWhileTalking(decreaseAudioPlayerVolumeWhileTalking);
|
||||
|
||||
if (change) {
|
||||
window.location.reload();
|
||||
}
|
||||
@@ -82,6 +101,10 @@
|
||||
localUserStore.setIgnoreFollowRequests(ignoreFollowRequests);
|
||||
}
|
||||
|
||||
function changeDecreaseAudioPlayerVolumeWhileTalking() {
|
||||
localUserStore.setDecreaseAudioPlayerVolumeWhileTalking(decreaseAudioPlayerVolumeWhileTalking);
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
menuVisiblilityStore.set(false);
|
||||
}
|
||||
@@ -154,6 +177,19 @@
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>{$LL.menu.settings.privacySettings.title()}</h3>
|
||||
<p>{$LL.menu.settings.privacySettings.explanation()}</p>
|
||||
<label>
|
||||
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={valueCameraPrivacySettings} />
|
||||
<span>{$LL.menu.settings.privacySettings.cameraToggle()}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={valueMicrophonePrivacySettings} />
|
||||
<span>{$LL.menu.settings.privacySettings.microphoneToggle()}</span>
|
||||
</label>
|
||||
</section>
|
||||
<section class="settings-section-save">
|
||||
<p>{$LL.menu.settings.save.warning()}</p>
|
||||
<button type="button" class="nes-btn is-primary" on:click|preventDefault={saveSetting}
|
||||
@@ -196,6 +232,15 @@
|
||||
on:change={changeIgnoreFollowRequests}
|
||||
/>
|
||||
<span>{$LL.menu.settings.ignoreFollowRequest()}</span>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="nes-checkbox is-dark"
|
||||
bind:checked={decreaseAudioPlayerVolumeWhileTalking}
|
||||
on:change={changeDecreaseAudioPlayerVolumeWhileTalking}
|
||||
/>
|
||||
<span>{$LL.audio.manager.reduce()}</span>
|
||||
</label>
|
||||
</label>
|
||||
</section>
|
||||
</div>
|
||||
@@ -217,12 +262,15 @@
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
section.settings-section-save {
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
|
||||
section.settings-section-noSaveOption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
transform: translate(-50%, 0);
|
||||
margin-top: 200px;
|
||||
max-width: 80vw;
|
||||
z-index: 350;
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 25 3 C 18.363281 3 13 8.363281 13 15 L 13 20 L 9 20 C 7.355469 20 6 21.355469 6 23 L 6 47 C 6 48.644531 7.355469 50 9 50 L 41 50 C 42.644531 50 44 48.644531 44 47 L 44 23 C 44 21.355469 42.644531 20 41 20 L 37 20 L 37 15 C 37 8.363281 31.636719 3 25 3 Z M 25 5 C 30.566406 5 35 9.433594 35 15 L 35 20 L 15 20 L 15 15 C 15 9.433594 19.433594 5 25 5 Z M 9 22 L 41 22 C 41.554688 22 42 22.445313 42 23 L 42 47 C 42 47.554688 41.554688 48 41 48 L 9 48 C 8.445313 48 8 47.554688 8 47 L 8 23 C 8 22.445313 8.445313 22 9 22 Z M 25 30 C 23.300781 30 22 31.300781 22 33 C 22 33.898438 22.398438 34.6875 23 35.1875 L 23 38 C 23 39.101563 23.898438 40 25 40 C 26.101563 40 27 39.101563 27 38 L 27 35.1875 C 27.601563 34.6875 28 33.898438 28 33 C 28 31.300781 26.699219 30 25 30 Z"/></svg>
|
||||
|
After Width: | Height: | Size: 891 B |
@@ -45,8 +45,10 @@ class ConnectionManager {
|
||||
|
||||
/**
|
||||
* TODO fix me to be move in game manager
|
||||
*
|
||||
* Returns the URL that we need to redirect to to load the OpenID screen, or "null" if no redirection needs to happen.
|
||||
*/
|
||||
public loadOpenIDScreen() {
|
||||
public loadOpenIDScreen(): URL | null {
|
||||
const state = localUserStore.generateState();
|
||||
const nonce = localUserStore.generateNonce();
|
||||
localUserStore.setAuthToken(null);
|
||||
@@ -55,11 +57,10 @@ class ConnectionManager {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
}
|
||||
const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`);
|
||||
const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`, window.location.href);
|
||||
redirectUrl.searchParams.append("state", state);
|
||||
redirectUrl.searchParams.append("nonce", nonce);
|
||||
redirectUrl.searchParams.append("playUri", this._currentRoom.key);
|
||||
window.location.assign(redirectUrl.toString());
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
@@ -83,8 +84,10 @@ class ConnectionManager {
|
||||
|
||||
/**
|
||||
* Tries to login to the node server and return the starting map url to be loaded
|
||||
*
|
||||
* @return returns a promise to the Room we are going to load OR a pointer to the URL we must redirect to if authentication is needed.
|
||||
*/
|
||||
public async initGameConnexion(): Promise<Room> {
|
||||
public async initGameConnexion(): Promise<Room | URL> {
|
||||
this.connexionType = urlManager.getGameConnexionType();
|
||||
this._currentRoom = null;
|
||||
|
||||
@@ -100,8 +103,9 @@ class ConnectionManager {
|
||||
|
||||
if (this.connexionType === GameConnexionTypes.login) {
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
if (this.loadOpenIDScreen() !== null) {
|
||||
return Promise.reject(new Error("You will be redirect on login page"));
|
||||
const redirect = this.loadOpenIDScreen();
|
||||
if (redirect !== null) {
|
||||
return redirect;
|
||||
}
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (this.connexionType === GameConnexionTypes.jwt) {
|
||||
@@ -124,8 +128,11 @@ class ConnectionManager {
|
||||
analyticsClient.loggedWithSso();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadOpenIDScreen();
|
||||
return Promise.reject(new Error("You will be redirect on login page"));
|
||||
const redirect = this.loadOpenIDScreen();
|
||||
if (redirect === null) {
|
||||
throw new Error("Unable to redirect on login page.");
|
||||
}
|
||||
return redirect;
|
||||
}
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
}
|
||||
@@ -213,8 +220,11 @@ class ConnectionManager {
|
||||
err.response?.data &&
|
||||
err.response.data !== "User cannot to be connected on openid provider")
|
||||
) {
|
||||
this.loadOpenIDScreen();
|
||||
return Promise.reject(new Error("You will be redirect on login page"));
|
||||
const redirect = this.loadOpenIDScreen();
|
||||
if (redirect === null) {
|
||||
throw new Error("Unable to redirect on login page.");
|
||||
}
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ export interface GroupCreatedUpdatedMessageInterface {
|
||||
position: PositionInterface;
|
||||
groupId: number;
|
||||
groupSize: number;
|
||||
locked: boolean;
|
||||
}
|
||||
|
||||
export interface GroupUsersUpdateMessageInterface {
|
||||
groupId: number;
|
||||
userIds: number[];
|
||||
}
|
||||
|
||||
export interface WebRtcDisconnectMessageInterface {
|
||||
|
||||
@@ -15,6 +15,7 @@ const helpCameraSettingsShown = "helpCameraSettingsShown";
|
||||
const fullscreenKey = "fullscreen";
|
||||
const forceCowebsiteTriggerKey = "forceCowebsiteTrigger";
|
||||
const ignoreFollowRequests = "ignoreFollowRequests";
|
||||
const decreaseAudioPlayerVolumeWhileTalking = "decreaseAudioPlayerVolumeWhileTalking";
|
||||
const lastRoomUrl = "lastRoomUrl";
|
||||
const authToken = "authToken";
|
||||
const state = "state";
|
||||
@@ -24,11 +25,14 @@ const code = "code";
|
||||
const cameraSetup = "cameraSetup";
|
||||
const cacheAPIIndex = "workavdenture-cache";
|
||||
const userProperties = "user-properties";
|
||||
const cameraPrivacySettings = "cameraPrivacySettings";
|
||||
const microphonePrivacySettings = "microphonePrivacySettings";
|
||||
|
||||
class LocalUserStore {
|
||||
saveUser(localUser: LocalUser) {
|
||||
localStorage.setItem("localUser", JSON.stringify(localUser));
|
||||
}
|
||||
|
||||
getLocalUser(): LocalUser | null {
|
||||
const data = localStorage.getItem("localUser");
|
||||
return data ? JSON.parse(data) : null;
|
||||
@@ -37,6 +41,7 @@ class LocalUserStore {
|
||||
setName(name: string): void {
|
||||
localStorage.setItem(playerNameKey, name);
|
||||
}
|
||||
|
||||
getName(): string | null {
|
||||
const value = localStorage.getItem(playerNameKey) || "";
|
||||
return isUserNameValid(value) ? value : null;
|
||||
@@ -45,6 +50,7 @@ class LocalUserStore {
|
||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||
localStorage.setItem(selectedPlayerKey, "" + playerCharacterIndex);
|
||||
}
|
||||
|
||||
getPlayerCharacterIndex(): number {
|
||||
return parseInt(localStorage.getItem(selectedPlayerKey) || "");
|
||||
}
|
||||
@@ -52,6 +58,7 @@ class LocalUserStore {
|
||||
setCustomCursorPosition(activeRow: number, selectedLayers: number[]): void {
|
||||
localStorage.setItem(customCursorPositionKey, JSON.stringify({ activeRow, selectedLayers }));
|
||||
}
|
||||
|
||||
getCustomCursorPosition(): { activeRow: number; selectedLayers: number[] } | null {
|
||||
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||
}
|
||||
@@ -59,6 +66,7 @@ class LocalUserStore {
|
||||
setCharacterLayers(layers: string[]): void {
|
||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
}
|
||||
|
||||
getCharacterLayers(): string[] | null {
|
||||
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||
return areCharacterLayersValid(value) ? value : null;
|
||||
@@ -67,6 +75,7 @@ class LocalUserStore {
|
||||
setCompanion(companion: string | null): void {
|
||||
return localStorage.setItem(companionKey, JSON.stringify(companion));
|
||||
}
|
||||
|
||||
getCompanion(): string | null {
|
||||
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
|
||||
|
||||
@@ -76,6 +85,7 @@ class LocalUserStore {
|
||||
|
||||
return companion;
|
||||
}
|
||||
|
||||
wasCompanionSet(): boolean {
|
||||
return localStorage.getItem(companionKey) ? true : false;
|
||||
}
|
||||
@@ -83,6 +93,7 @@ class LocalUserStore {
|
||||
setGameQualityValue(value: number): void {
|
||||
localStorage.setItem(gameQualityKey, "" + value);
|
||||
}
|
||||
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(gameQualityKey) || "60");
|
||||
}
|
||||
@@ -90,6 +101,7 @@ class LocalUserStore {
|
||||
setVideoQualityValue(value: number): void {
|
||||
localStorage.setItem(videoQualityKey, "" + value);
|
||||
}
|
||||
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(videoQualityKey) || "20");
|
||||
}
|
||||
@@ -97,6 +109,7 @@ class LocalUserStore {
|
||||
setAudioPlayerVolume(value: number): void {
|
||||
localStorage.setItem(audioPlayerVolumeKey, "" + value);
|
||||
}
|
||||
|
||||
getAudioPlayerVolume(): number {
|
||||
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || "1");
|
||||
}
|
||||
@@ -104,6 +117,7 @@ class LocalUserStore {
|
||||
setAudioPlayerMuted(value: boolean): void {
|
||||
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||
}
|
||||
|
||||
getAudioPlayerMuted(): boolean {
|
||||
return localStorage.getItem(audioPlayerMuteKey) === "true";
|
||||
}
|
||||
@@ -111,6 +125,7 @@ class LocalUserStore {
|
||||
setHelpCameraSettingsShown(): void {
|
||||
localStorage.setItem(helpCameraSettingsShown, "1");
|
||||
}
|
||||
|
||||
getHelpCameraSettingsShown(): boolean {
|
||||
return localStorage.getItem(helpCameraSettingsShown) === "1";
|
||||
}
|
||||
@@ -118,6 +133,7 @@ class LocalUserStore {
|
||||
setFullscreen(value: boolean): void {
|
||||
localStorage.setItem(fullscreenKey, value.toString());
|
||||
}
|
||||
|
||||
getFullscreen(): boolean {
|
||||
return localStorage.getItem(fullscreenKey) === "true";
|
||||
}
|
||||
@@ -125,6 +141,7 @@ class LocalUserStore {
|
||||
setForceCowebsiteTrigger(value: boolean): void {
|
||||
localStorage.setItem(forceCowebsiteTriggerKey, value.toString());
|
||||
}
|
||||
|
||||
getForceCowebsiteTrigger(): boolean {
|
||||
return localStorage.getItem(forceCowebsiteTriggerKey) === "true";
|
||||
}
|
||||
@@ -132,9 +149,16 @@ class LocalUserStore {
|
||||
setIgnoreFollowRequests(value: boolean): void {
|
||||
localStorage.setItem(ignoreFollowRequests, value.toString());
|
||||
}
|
||||
|
||||
getIgnoreFollowRequests(): boolean {
|
||||
return localStorage.getItem(ignoreFollowRequests) === "true";
|
||||
}
|
||||
setDecreaseAudioPlayerVolumeWhileTalking(value: boolean): void {
|
||||
localStorage.setItem(decreaseAudioPlayerVolumeWhileTalking, value.toString());
|
||||
}
|
||||
getDecreaseAudioPlayerVolumeWhileTalking(): boolean {
|
||||
return localStorage.getItem(decreaseAudioPlayerVolumeWhileTalking) === "true";
|
||||
}
|
||||
|
||||
async setLastRoomUrl(roomUrl: string): Promise<void> {
|
||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||
@@ -148,11 +172,13 @@ class LocalUserStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLastRoomUrl(): string {
|
||||
return (
|
||||
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
|
||||
);
|
||||
}
|
||||
|
||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||
if (!("caches" in window)) {
|
||||
return Promise.resolve(undefined);
|
||||
@@ -169,6 +195,7 @@ class LocalUserStore {
|
||||
setAuthToken(value: string | null) {
|
||||
value ? localStorage.setItem(authToken, value) : localStorage.removeItem(authToken);
|
||||
}
|
||||
|
||||
getAuthToken(): string | null {
|
||||
return localStorage.getItem(authToken);
|
||||
}
|
||||
@@ -195,23 +222,29 @@ class LocalUserStore {
|
||||
}
|
||||
return oldValue === value;
|
||||
}
|
||||
|
||||
setState(value: string) {
|
||||
localStorage.setItem(state, value);
|
||||
}
|
||||
|
||||
getState(): string | null {
|
||||
return localStorage.getItem(state);
|
||||
}
|
||||
|
||||
generateNonce(): string {
|
||||
const newNonce = uuidv4();
|
||||
localStorage.setItem(nonce, newNonce);
|
||||
return newNonce;
|
||||
}
|
||||
|
||||
getNonce(): string | null {
|
||||
return localStorage.getItem(nonce);
|
||||
}
|
||||
|
||||
setCode(value: string): void {
|
||||
localStorage.setItem(code, value);
|
||||
}
|
||||
|
||||
getCode(): string | null {
|
||||
return localStorage.getItem(code);
|
||||
}
|
||||
@@ -219,11 +252,36 @@ class LocalUserStore {
|
||||
setCameraSetup(cameraId: string) {
|
||||
localStorage.setItem(cameraSetup, cameraId);
|
||||
}
|
||||
|
||||
getCameraSetup(): { video: unknown; audio: unknown } | undefined {
|
||||
const cameraSetupValues = localStorage.getItem(cameraSetup);
|
||||
return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined;
|
||||
}
|
||||
|
||||
setCameraPrivacySettings(option: boolean) {
|
||||
localStorage.setItem(cameraPrivacySettings, option.toString());
|
||||
}
|
||||
|
||||
getCameraPrivacySettings() {
|
||||
//if this setting doesn't exist in LocalUserStore, we set a default value
|
||||
if (localStorage.getItem(cameraPrivacySettings) == null) {
|
||||
localStorage.setItem(cameraPrivacySettings, "false");
|
||||
}
|
||||
return localStorage.getItem(cameraPrivacySettings) === "true";
|
||||
}
|
||||
|
||||
setMicrophonePrivacySettings(option: boolean) {
|
||||
localStorage.setItem(microphonePrivacySettings, option.toString());
|
||||
}
|
||||
|
||||
getMicrophonePrivacySettings() {
|
||||
//if this setting doesn't exist in LocalUserStore, we set a default value
|
||||
if (localStorage.getItem(microphonePrivacySettings) == null) {
|
||||
localStorage.setItem(microphonePrivacySettings, "true");
|
||||
}
|
||||
return localStorage.getItem(microphonePrivacySettings) === "true";
|
||||
}
|
||||
|
||||
getAllUserProperties(): Map<string, unknown> {
|
||||
const result = new Map<string, string>();
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
|
||||
@@ -31,6 +31,8 @@ export class Room {
|
||||
private _group: string | null = null;
|
||||
private _expireOn: Date | undefined;
|
||||
private _canReport: boolean = false;
|
||||
private _loadingLogo: string | undefined;
|
||||
private _loginSceneLogo: string | undefined;
|
||||
|
||||
private constructor(private roomUrl: URL) {
|
||||
this.id = roomUrl.pathname;
|
||||
@@ -126,6 +128,8 @@ export class Room {
|
||||
this._expireOn = new Date(data.expireOn);
|
||||
}
|
||||
this._canReport = data.canReport ?? false;
|
||||
this._loadingLogo = data.loadingLogo ?? undefined;
|
||||
this._loginSceneLogo = data.loginSceneLogo ?? undefined;
|
||||
return new MapDetail(data.mapUrl);
|
||||
} else {
|
||||
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
|
||||
@@ -233,4 +237,12 @@ export class Room {
|
||||
get canReport(): boolean {
|
||||
return this._canReport;
|
||||
}
|
||||
|
||||
get loadingLogo(): string | undefined {
|
||||
return this._loadingLogo;
|
||||
}
|
||||
|
||||
get loginSceneLogo(): string | undefined {
|
||||
return this._loginSceneLogo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,47 +5,35 @@ import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
||||
import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
|
||||
import type {
|
||||
GroupCreatedUpdatedMessageInterface,
|
||||
ItemEventMessageInterface,
|
||||
GroupUsersUpdateMessageInterface,
|
||||
MessageUserJoined,
|
||||
OnConnectInterface,
|
||||
PlayerDetailsUpdatedMessageInterface,
|
||||
PlayGlobalMessageInterface,
|
||||
PositionInterface,
|
||||
RoomJoinedMessageInterface,
|
||||
ViewportInterface,
|
||||
WebRtcDisconnectMessageInterface,
|
||||
WebRtcSignalReceivedMessageInterface,
|
||||
} from "./ConnexionModels";
|
||||
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
||||
import { adminMessagesService } from "./AdminMessagesService";
|
||||
import { connectionManager } from "./ConnectionManager";
|
||||
import { get } from "svelte/store";
|
||||
import { followRoleStore, followUsersStore } from "../Stores/FollowStore";
|
||||
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
|
||||
import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore";
|
||||
import { localUserStore } from "./LocalUserStore";
|
||||
import {
|
||||
RefreshRoomMessage,
|
||||
ServerToClientMessage as ServerToClientMessageTsProto,
|
||||
TokenExpiredMessage,
|
||||
WorldConnexionMessage,
|
||||
WorldFullMessage,
|
||||
ErrorMessage as ErrorMessageTsProto,
|
||||
UserMovedMessage as UserMovedMessageTsProto,
|
||||
GroupUpdateMessage as GroupUpdateMessageTsProto,
|
||||
GroupDeleteMessage as GroupDeleteMessageTsProto,
|
||||
UserJoinedMessage as UserJoinedMessageTsProto,
|
||||
UserLeftMessage as UserLeftMessageTsProto,
|
||||
ItemEventMessage as ItemEventMessageTsProto,
|
||||
EmoteEventMessage as EmoteEventMessageTsProto,
|
||||
VariableMessage as VariableMessageTsProto,
|
||||
PlayerDetailsUpdatedMessage as PlayerDetailsUpdatedMessageTsProto,
|
||||
WorldFullWarningMessage,
|
||||
WebRtcDisconnectMessage as WebRtcDisconnectMessageTsProto,
|
||||
PlayGlobalMessage as PlayGlobalMessageTsProto,
|
||||
StopGlobalMessage as StopGlobalMessageTsProto,
|
||||
SendJitsiJwtMessage as SendJitsiJwtMessageTsProto,
|
||||
SendUserMessage as SendUserMessageTsProto,
|
||||
BanUserMessage as BanUserMessageTsProto,
|
||||
ClientToServerMessage as ClientToServerMessageTsProto,
|
||||
PositionMessage as PositionMessageTsProto,
|
||||
ViewportMessage as ViewportMessageTsProto,
|
||||
@@ -55,8 +43,6 @@ import {
|
||||
CharacterLayerMessage,
|
||||
} from "../Messages/ts-proto-generated/messages";
|
||||
import { Subject } from "rxjs";
|
||||
import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent";
|
||||
import { match } from "assert";
|
||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||
import { gameManager } from "../Phaser/Game/GameManager";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
|
||||
@@ -116,6 +102,9 @@ export class RoomConnection implements RoomConnection {
|
||||
private readonly _groupUpdateMessageStream = new Subject<GroupCreatedUpdatedMessageInterface>();
|
||||
public readonly groupUpdateMessageStream = this._groupUpdateMessageStream.asObservable();
|
||||
|
||||
private readonly _groupUsersUpdateMessageStream = new Subject<GroupUsersUpdateMessageInterface>();
|
||||
public readonly groupUsersUpdateMessageStream = this._groupUsersUpdateMessageStream.asObservable();
|
||||
|
||||
private readonly _groupDeleteMessageStream = new Subject<GroupDeleteMessageTsProto>();
|
||||
public readonly groupDeleteMessageStream = this._groupDeleteMessageStream.asObservable();
|
||||
|
||||
@@ -220,6 +209,7 @@ export class RoomConnection implements RoomConnection {
|
||||
|
||||
this.socket.onmessage = (messageEvent) => {
|
||||
const arrayBuffer: ArrayBuffer = messageEvent.data;
|
||||
const initCharacterLayers = characterLayers;
|
||||
|
||||
const serverToClientMessage = ServerToClientMessageTsProto.decode(new Uint8Array(arrayBuffer));
|
||||
//const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
||||
@@ -342,14 +332,16 @@ export class RoomConnection implements RoomConnection {
|
||||
this._userRoomToken = roomJoinedMessage.userRoomToken;
|
||||
|
||||
// If one of the URLs sent to us does not exist, let's go to the Woka selection screen.
|
||||
for (const characterLayer of roomJoinedMessage.characterLayer) {
|
||||
if (!characterLayer.url) {
|
||||
this.goToSelectYourWokaScene();
|
||||
this.closed = true;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
roomJoinedMessage.characterLayer.length !== initCharacterLayers.length ||
|
||||
roomJoinedMessage.characterLayer.find((layer) => !layer.url)
|
||||
) {
|
||||
this.goToSelectYourWokaScene();
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
if (this.closed) {
|
||||
this.closeConnection();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -440,6 +432,10 @@ export class RoomConnection implements RoomConnection {
|
||||
this._sendJitsiJwtMessageStream.next(message.sendJitsiJwtMessage);
|
||||
break;
|
||||
}
|
||||
case "groupUsersUpdateMessage": {
|
||||
this._groupUsersUpdateMessageStream.next(message.groupUsersUpdateMessage);
|
||||
break;
|
||||
}
|
||||
case "sendUserMessage": {
|
||||
adminMessagesService.onSendusermessage(message.sendUserMessage);
|
||||
break;
|
||||
@@ -672,6 +668,7 @@ export class RoomConnection implements RoomConnection {
|
||||
groupId: message.groupId,
|
||||
position: position,
|
||||
groupSize: message.groupSize,
|
||||
locked: message.locked,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -887,6 +884,19 @@ export class RoomConnection implements RoomConnection {
|
||||
this.socket.send(bytes);
|
||||
}
|
||||
|
||||
public emitLockGroup(lock: boolean = true): void {
|
||||
const bytes = ClientToServerMessageTsProto.encode({
|
||||
message: {
|
||||
$case: "lockGroupPromptMessage",
|
||||
lockGroupPromptMessage: {
|
||||
lock,
|
||||
},
|
||||
},
|
||||
}).finish();
|
||||
|
||||
this.socket.send(bytes);
|
||||
}
|
||||
|
||||
public getAllTags(): string[] {
|
||||
return this.tags;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
||||
import { DirtyScene } from "../Game/DirtyScene";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
|
||||
const LogoNameIndex: string = "logoLoading";
|
||||
const TextName: string = "Loading...";
|
||||
const LogoResource: string = "static/images/logo.png";
|
||||
const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 };
|
||||
|
||||
const loadingBarHeight: number = 16;
|
||||
const padding: number = 5;
|
||||
@@ -14,9 +16,15 @@ export class Loader {
|
||||
private progress!: Phaser.GameObjects.Graphics;
|
||||
private progressAmount: number = 0;
|
||||
private logo: Phaser.GameObjects.Image | undefined;
|
||||
private logoPoweredBy: Phaser.GameObjects.Image | undefined;
|
||||
private poweredByLogo: Phaser.GameObjects.Image | undefined;
|
||||
private loadingText: Phaser.GameObjects.Text | null = null;
|
||||
private logoNameIndex!: string;
|
||||
private superLoad: SuperLoaderPlugin;
|
||||
|
||||
public constructor(private scene: Phaser.Scene) {}
|
||||
public constructor(private scene: Phaser.Scene) {
|
||||
this.superLoad = new SuperLoaderPlugin(scene);
|
||||
}
|
||||
|
||||
public addLoader(): void {
|
||||
// If there is nothing to load, do not display the loader.
|
||||
@@ -24,43 +32,53 @@ export class Loader {
|
||||
return;
|
||||
}
|
||||
|
||||
const logoResource = gameManager.currentStartedRoom.loadingLogo ?? "static/images/logo.png";
|
||||
this.logoNameIndex = "logoLoading" + logoResource;
|
||||
|
||||
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
|
||||
|
||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
return res(
|
||||
(this.logo = this.scene.add.image(
|
||||
this.scene.game.renderer.width / 2,
|
||||
this.scene.game.renderer.height / 2 - 150,
|
||||
LogoNameIndex
|
||||
))
|
||||
);
|
||||
} else {
|
||||
//add loading if logo image is not ready
|
||||
this.loadingText = this.scene.add.text(
|
||||
//add loading if logo image until logo image is ready
|
||||
this.loadingText = this.scene.add.text(
|
||||
this.scene.game.renderer.width / 2,
|
||||
this.scene.game.renderer.height / 2 - 50,
|
||||
TextName
|
||||
);
|
||||
|
||||
const logoPromise = this.superLoad.image(this.logoNameIndex, logoResource);
|
||||
logoPromise
|
||||
.then((texture) => {
|
||||
this.logo = this.scene.add.image(
|
||||
this.scene.game.renderer.width / 2,
|
||||
this.scene.game.renderer.height / 2 - 50,
|
||||
TextName
|
||||
this.scene.game.renderer.height / 2 - 150,
|
||||
texture
|
||||
);
|
||||
}
|
||||
this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||
this.scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||
if (this.loadingText) {
|
||||
this.loadingText.destroy();
|
||||
}
|
||||
return res(
|
||||
(this.logo = this.scene.add.image(
|
||||
|
||||
this.loadingText?.destroy();
|
||||
})
|
||||
.catch((e) => console.warn("Could not load logo: ", logoResource, e));
|
||||
|
||||
let poweredByLogoPromise: CancelablePromise<Texture> | undefined;
|
||||
if (gameManager.currentStartedRoom.loadingLogo) {
|
||||
poweredByLogoPromise = this.superLoad.image(
|
||||
"poweredByLogo",
|
||||
"static/images/Powered_By_WorkAdventure_Small.png"
|
||||
);
|
||||
poweredByLogoPromise
|
||||
.then((texture) => {
|
||||
this.poweredByLogo = this.scene.add.image(
|
||||
this.scene.game.renderer.width / 2,
|
||||
this.scene.game.renderer.height / 2 - 150,
|
||||
LogoNameIndex
|
||||
))
|
||||
this.scene.game.renderer.height - 50,
|
||||
texture
|
||||
);
|
||||
})
|
||||
.catch((e) =>
|
||||
console.warn('Could not load image "static/images/Powered_By_WorkAdventure_Small.png"', e)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.progressContainer = this.scene.add.graphics();
|
||||
this.progress = this.scene.add.graphics();
|
||||
this.progressContainer.fillStyle(0x444444, 0.8);
|
||||
this.progress = this.scene.add.graphics();
|
||||
|
||||
this.resize();
|
||||
|
||||
@@ -68,26 +86,35 @@ export class Loader {
|
||||
this.progressAmount = value;
|
||||
this.drawProgress();
|
||||
});
|
||||
|
||||
const resizeFunction = this.resize.bind(this);
|
||||
this.scene.scale.on(Phaser.Scale.Events.RESIZE, resizeFunction);
|
||||
|
||||
this.scene.load.on("complete", () => {
|
||||
if (this.loadingText) {
|
||||
this.loadingText.destroy();
|
||||
}
|
||||
promiseLoadLogoTexture
|
||||
.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||
resLoadingImage.destroy();
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
logoPromise.cancel();
|
||||
poweredByLogoPromise?.cancel();
|
||||
|
||||
this.logo?.destroy();
|
||||
this.poweredByLogo?.destroy();
|
||||
|
||||
this.progress.destroy();
|
||||
this.progressContainer.destroy();
|
||||
if (this.scene instanceof DirtyScene) {
|
||||
this.scene.markDirty();
|
||||
}
|
||||
this.scene.scale.off(Phaser.Scale.Events.RESIZE, resizeFunction);
|
||||
});
|
||||
}
|
||||
|
||||
public removeLoader(): void {
|
||||
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
this.scene.load.textureManager.remove(LogoNameIndex);
|
||||
if (this.scene.load.textureManager.exists(this.logoNameIndex)) {
|
||||
this.scene.load.textureManager.remove(this.logoNameIndex);
|
||||
}
|
||||
if (this.scene.load.textureManager.exists("poweredByLogo")) {
|
||||
this.scene.load.textureManager.remove("poweredByLogo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +141,11 @@ export class Loader {
|
||||
this.logo.x = this.scene.game.renderer.width / 2;
|
||||
this.logo.y = this.scene.game.renderer.height / 2 - 150;
|
||||
}
|
||||
|
||||
if (this.poweredByLogo) {
|
||||
this.poweredByLogo.x = this.scene.game.renderer.width / 2;
|
||||
this.poweredByLogo.y = this.scene.game.renderer.height - 40;
|
||||
}
|
||||
}
|
||||
|
||||
private drawProgress() {
|
||||
|
||||
@@ -57,8 +57,8 @@ export class SoundMeter {
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
|
||||
this.analyser.fftSize = 2048;
|
||||
const bufferLength = this.analyser.fftSize;
|
||||
this.analyser.fftSize = 256;
|
||||
const bufferLength = this.analyser.frequencyBinCount;
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { createColorStore } from "../../Stores/OutlineColorStore";
|
||||
import type { OutlineableInterface } from "../Game/OutlineableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import { TalkIcon } from "../Components/TalkIcon";
|
||||
import { Deferred } from "ts-deferred";
|
||||
|
||||
const playerNameY = -25;
|
||||
|
||||
@@ -50,6 +51,11 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
|
||||
private texturePromise: CancelablePromise<string[] | void> | undefined;
|
||||
|
||||
/**
|
||||
* A deferred promise that resolves when the texture of the character is actually displayed.
|
||||
*/
|
||||
private textureLoadedDeferred = new Deferred<void>();
|
||||
|
||||
constructor(
|
||||
scene: GameScene,
|
||||
x: number,
|
||||
@@ -78,12 +84,13 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
this.textureLoadedDeferred.resolve();
|
||||
return this.getSnapshot().then((htmlImageElementSrc) => {
|
||||
this._pictureStore.set(htmlImageElementSrc);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
return lazyLoadPlayerCharacterTextures(scene.load, [
|
||||
return lazyLoadPlayerCharacterTextures(scene.superLoad, [
|
||||
{
|
||||
id: "color_22",
|
||||
img: "resources/customisation/character_color/character_color21.png",
|
||||
@@ -92,11 +99,20 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
id: "eyes_23",
|
||||
img: "resources/customisation/character_eyes/character_eyes23.png",
|
||||
},
|
||||
]).then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
});
|
||||
])
|
||||
.then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
this.textureLoadedDeferred.resolve();
|
||||
return this.getSnapshot().then((htmlImageElementSrc) => {
|
||||
this._pictureStore.set(htmlImageElementSrc);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
this.textureLoadedDeferred.reject(e);
|
||||
throw e;
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.texturePromise = undefined;
|
||||
@@ -517,4 +533,13 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
public characterFarAwayOutline(): void {
|
||||
this.outlineColorStore.characterFarAway();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves as soon as a texture is displayed for the user.
|
||||
* The promise will return when the required texture is loaded OR when the fallback texture is loaded (in case
|
||||
* the required texture could not be loaded).
|
||||
*/
|
||||
public getTextureLoadedPromise(): PromiseLike<void> {
|
||||
return this.textureLoadedDeferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//The list of all the player textures, both the default models and the partial textures used for customization
|
||||
|
||||
import { WokaList, WokaPartType } from "../../Messages/JsonMessages/PlayerTextures";
|
||||
|
||||
export interface BodyResourceDescriptionListInterface {
|
||||
[key: string]: BodyResourceDescriptionInterface;
|
||||
}
|
||||
@@ -33,24 +35,6 @@ export enum PlayerTexturesKey {
|
||||
Woka = "woka",
|
||||
}
|
||||
|
||||
type PlayerTexturesMetadata = Record<PlayerTexturesKey, PlayerTexturesCategory>;
|
||||
|
||||
interface PlayerTexturesCategory {
|
||||
collections: PlayerTexturesCollection[];
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface PlayerTexturesCollection {
|
||||
name: string;
|
||||
textures: PlayerTexturesRecord[];
|
||||
}
|
||||
|
||||
interface PlayerTexturesRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class PlayerTextures {
|
||||
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
@@ -61,7 +45,7 @@ export class PlayerTextures {
|
||||
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private LAYERS: BodyResourceDescriptionListInterface[] = [];
|
||||
|
||||
public loadPlayerTexturesMetadata(metadata: PlayerTexturesMetadata): void {
|
||||
public loadPlayerTexturesMetadata(metadata: WokaList): void {
|
||||
this.mapTexturesMetadataIntoResources(metadata);
|
||||
}
|
||||
|
||||
@@ -88,7 +72,7 @@ export class PlayerTextures {
|
||||
return this.LAYERS;
|
||||
}
|
||||
|
||||
private mapTexturesMetadataIntoResources(metadata: PlayerTexturesMetadata): void {
|
||||
private mapTexturesMetadataIntoResources(metadata: WokaList): void {
|
||||
this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka);
|
||||
this.COLOR_RESOURCES = this.getMappedResources(metadata.body);
|
||||
this.EYES_RESOURCES = this.getMappedResources(metadata.eyes);
|
||||
@@ -107,7 +91,7 @@ export class PlayerTextures {
|
||||
];
|
||||
}
|
||||
|
||||
private getMappedResources(category: PlayerTexturesCategory): BodyResourceDescriptionListInterface {
|
||||
private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
|
||||
const resources: BodyResourceDescriptionListInterface = {};
|
||||
if (!category) {
|
||||
return {};
|
||||
|
||||
@@ -2,6 +2,8 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
|
||||
export interface FrameConfig {
|
||||
frameWidth: number;
|
||||
@@ -35,81 +37,33 @@ export const loadAllDefaultModels = (
|
||||
};
|
||||
|
||||
export const loadWokaTexture = (
|
||||
loaderPlugin: LoaderPlugin,
|
||||
superLoaderPlugin: SuperLoaderPlugin,
|
||||
texture: BodyResourceDescriptionInterface
|
||||
): CancelablePromise<BodyResourceDescriptionInterface> => {
|
||||
return createLoadingPromise(loaderPlugin, texture, {
|
||||
): CancelablePromise<Texture> => {
|
||||
return superLoaderPlugin.spritesheet(texture.id, texture.img, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
});
|
||||
};
|
||||
|
||||
export const lazyLoadPlayerCharacterTextures = (
|
||||
loadPlugin: LoaderPlugin,
|
||||
superLoaderPlugin: SuperLoaderPlugin,
|
||||
textures: BodyResourceDescriptionInterface[]
|
||||
): CancelablePromise<string[]> => {
|
||||
const promisesList: CancelablePromise<unknown>[] = [];
|
||||
textures.forEach((texture) => {
|
||||
try {
|
||||
//TODO refactor
|
||||
if (!loadPlugin.textureManager.exists(texture.id)) {
|
||||
promisesList.push(
|
||||
createLoadingPromise(loadPlugin, texture, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
|
||||
if (promisesList.length > 0) {
|
||||
loadPlugin.start();
|
||||
returnPromise = CancelablePromise.all(promisesList).then(() => textures);
|
||||
} else {
|
||||
returnPromise = CancelablePromise.resolve(textures);
|
||||
const promisesList: CancelablePromise<Texture>[] = [];
|
||||
for (const texture of textures) {
|
||||
promisesList.push(
|
||||
superLoaderPlugin.spritesheet(texture.id, texture.img, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
})
|
||||
);
|
||||
}
|
||||
const returnPromise: CancelablePromise<Texture[]> = CancelablePromise.all(promisesList);
|
||||
|
||||
//If the loading fail, we render the default model instead.
|
||||
return returnPromise.then((keys) =>
|
||||
keys.map((key) => {
|
||||
return typeof key !== "string" ? key.id : key;
|
||||
return returnPromise.then(() =>
|
||||
textures.map((key) => {
|
||||
return key.id;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const createLoadingPromise = (
|
||||
loadPlugin: LoaderPlugin,
|
||||
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
||||
frameConfig: FrameConfig
|
||||
) => {
|
||||
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.id)) {
|
||||
return res(playerResourceDescriptor);
|
||||
}
|
||||
|
||||
cancel(() => {
|
||||
loadPlugin.off("loaderror");
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id);
|
||||
return;
|
||||
});
|
||||
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.id, playerResourceDescriptor.img, frameConfig);
|
||||
const errorCallback = (file: { src: string }) => {
|
||||
if (file.src !== playerResourceDescriptor.img) return;
|
||||
console.error("failed loading player resource: ", playerResourceDescriptor);
|
||||
rej(playerResourceDescriptor);
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
|
||||
loadPlugin.off("loaderror", errorCallback);
|
||||
};
|
||||
const successCallback = () => {
|
||||
loadPlugin.off("loaderror", errorCallback);
|
||||
res(playerResourceDescriptor);
|
||||
};
|
||||
|
||||
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
|
||||
loadPlugin.on("loaderror", errorCallback);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { EnableCameraSceneName } from "../Login/EnableCameraScene";
|
||||
import { LoginSceneName } from "../Login/LoginScene";
|
||||
import { SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||
import { GameScene } from "./GameScene";
|
||||
import { EmptySceneName } from "../Login/EmptyScene";
|
||||
|
||||
/**
|
||||
* This class should be responsible for any scene starting/stopping
|
||||
@@ -19,7 +20,7 @@ export class GameManager {
|
||||
private companion: string | null;
|
||||
private startRoom!: Room;
|
||||
private cameraSetup?: { video: unknown; audio: unknown };
|
||||
currentGameSceneName: string | null = null;
|
||||
private currentGameSceneName: string | null = null;
|
||||
// Note: this scenePlugin is the scenePlugin of the EntryScene. We should always provide a key in methods called on this scenePlugin.
|
||||
private scenePlugin!: Phaser.Scenes.ScenePlugin;
|
||||
|
||||
@@ -32,7 +33,14 @@ export class GameManager {
|
||||
|
||||
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
|
||||
this.scenePlugin = scenePlugin;
|
||||
this.startRoom = await connectionManager.initGameConnexion();
|
||||
const result = await connectionManager.initGameConnexion();
|
||||
if (result instanceof URL) {
|
||||
window.location.assign(result.toString());
|
||||
// window.location.assign is not immediate and Javascript keeps running after.
|
||||
// so we need to redirect to an empty Phaser scene, waiting for the redirection to take place
|
||||
return EmptySceneName;
|
||||
}
|
||||
this.startRoom = result;
|
||||
this.loadMap(this.startRoom);
|
||||
|
||||
//If player name was not set show login scene with player name
|
||||
|
||||
@@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager";
|
||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||
|
||||
import { lazyLoadPlayerCharacterTextures, loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { lazyLoadPlayerCharacterTextures } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||
@@ -76,6 +76,7 @@ import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
import { contactPageStore } from "../../Stores/MenuStore";
|
||||
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
|
||||
import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
|
||||
import { currentPlayerGroupIdStore, currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore";
|
||||
|
||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
@@ -97,8 +98,10 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
import { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import type { VideoPeer } from "../../WebRtc/VideoPeer";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { Deferred } from "ts-deferred";
|
||||
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
reconnecting: boolean;
|
||||
@@ -164,13 +167,9 @@ export class GameScene extends DirtyScene {
|
||||
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
||||
public connection: RoomConnection | undefined;
|
||||
private simplePeer!: SimplePeer;
|
||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||
private connectionAnswerPromiseResolve!: (
|
||||
value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>
|
||||
) => void;
|
||||
private connectionAnswerPromiseDeferred: Deferred<RoomJoinedMessageInterface>;
|
||||
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
||||
private createPromise: Promise<void>;
|
||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||
private createPromiseDeferred: Deferred<void>;
|
||||
private iframeSubscriptionList!: Array<Subscription>;
|
||||
private peerStoreUnsubscribe!: Unsubscriber;
|
||||
private emoteUnsubscribe!: Unsubscriber;
|
||||
@@ -179,6 +178,7 @@ export class GameScene extends DirtyScene {
|
||||
private volumeStoreUnsubscribers: Map<number, Unsubscriber> = new Map<number, Unsubscriber>();
|
||||
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
|
||||
private followUsersColorStoreUnsubscribe!: Unsubscriber;
|
||||
private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber;
|
||||
|
||||
private biggestAvailableAreaStoreUnsubscribe!: () => void;
|
||||
MapUrlFile: string;
|
||||
@@ -220,6 +220,8 @@ export class GameScene extends DirtyScene {
|
||||
private loader: Loader;
|
||||
private lastCameraEvent: WasCameraUpdatedEvent | undefined;
|
||||
private firstCameraUpdateSent: boolean = false;
|
||||
private currentPlayerGroupId?: number;
|
||||
public readonly superLoad: SuperLoaderPlugin;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||
super({
|
||||
@@ -232,13 +234,10 @@ export class GameScene extends DirtyScene {
|
||||
this.MapUrlFile = MapUrlFile;
|
||||
this.roomUrl = room.key;
|
||||
|
||||
this.createPromise = new Promise<void>((resolve, reject): void => {
|
||||
this.createPromiseResolve = resolve;
|
||||
});
|
||||
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
||||
this.connectionAnswerPromiseResolve = resolve;
|
||||
});
|
||||
this.createPromiseDeferred = new Deferred<void>();
|
||||
this.connectionAnswerPromiseDeferred = new Deferred<RoomJoinedMessageInterface>();
|
||||
this.loader = new Loader(this);
|
||||
this.superLoad = new SuperLoaderPlugin(this);
|
||||
}
|
||||
|
||||
//hook preload scene
|
||||
@@ -408,11 +407,11 @@ export class GameScene extends DirtyScene {
|
||||
this.load.on("complete", () => {
|
||||
// FIXME: the factory might fail because the resources might not be loaded yet...
|
||||
// We would need to add a loader ended event in addition to the createPromise
|
||||
this.createPromise
|
||||
this.createPromiseDeferred.promise
|
||||
.then(async () => {
|
||||
itemFactory.create(this);
|
||||
|
||||
const roomJoinedAnswer = await this.connectionAnswerPromise;
|
||||
const roomJoinedAnswer = await this.connectionAnswerPromiseDeferred.promise;
|
||||
|
||||
for (const object of objectsOfType) {
|
||||
// TODO: we should pass here a factory to create sprites (maybe?)
|
||||
@@ -609,7 +608,7 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
this.createPromiseResolve();
|
||||
this.createPromiseDeferred.resolve();
|
||||
// Now, let's load the script, if any
|
||||
const scripts = this.getScriptUrls(this.mapFile);
|
||||
const disableModuleMode = this.getProperty(this.mapFile, GameMapProperties.SCRIPT_DISABLE_MODULE_SUPPORT) as
|
||||
@@ -635,7 +634,7 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
|
||||
const talkIconVolumeTreshold = 10;
|
||||
let oldPeerNumber = 0;
|
||||
const oldPeers = new Map<number, VideoPeer>();
|
||||
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
|
||||
this.volumeStoreUnsubscribers.forEach((unsubscribe) => unsubscribe());
|
||||
this.volumeStoreUnsubscribers.clear();
|
||||
@@ -652,10 +651,17 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
|
||||
const newPeerNumber = peers.size;
|
||||
if (newPeerNumber > oldPeerNumber) {
|
||||
if (newPeerNumber > oldPeers.size) {
|
||||
this.playSound("audio-webrtc-in");
|
||||
} else if (newPeerNumber < oldPeerNumber) {
|
||||
} else if (newPeerNumber < oldPeers.size) {
|
||||
this.playSound("audio-webrtc-out");
|
||||
const oldPeersKeys = oldPeers.keys();
|
||||
const newPeersKeys = Array.from(peers.keys());
|
||||
for (const oldKey of oldPeersKeys) {
|
||||
if (!newPeersKeys.includes(oldKey)) {
|
||||
this.MapPlayersByKey.get(oldKey)?.showTalkIcon(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newPeerNumber > 0) {
|
||||
if (!this.localVolumeStoreUnsubscriber) {
|
||||
@@ -673,7 +679,10 @@ export class GameScene extends DirtyScene {
|
||||
this.localVolumeStoreUnsubscriber = undefined;
|
||||
}
|
||||
}
|
||||
oldPeerNumber = newPeerNumber;
|
||||
oldPeers.clear();
|
||||
for (const [key, val] of peers) {
|
||||
oldPeers.set(key, val);
|
||||
}
|
||||
});
|
||||
|
||||
this.emoteUnsubscribe = emoteStore.subscribe((emote) => {
|
||||
@@ -702,7 +711,15 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises])
|
||||
this.currentPlayerGroupIdStoreUnsubscribe = currentPlayerGroupIdStore.subscribe((groupId) => {
|
||||
this.currentPlayerGroupId = groupId;
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
|
||||
...scriptPromises,
|
||||
this.CurrentPlayer.getTextureLoadedPromise() as Promise<unknown>,
|
||||
])
|
||||
.then(() => {
|
||||
this.scene.wake();
|
||||
})
|
||||
@@ -739,7 +756,7 @@ export class GameScene extends DirtyScene {
|
||||
.then((onConnect: OnConnectInterface) => {
|
||||
this.connection = onConnect.connection;
|
||||
|
||||
lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers)
|
||||
lazyLoadPlayerCharacterTextures(this.superLoad, onConnect.room.characterLayers)
|
||||
.then((layers) => {
|
||||
this.currentPlayerTexturesResolve(layers);
|
||||
})
|
||||
@@ -829,6 +846,11 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
});
|
||||
|
||||
this.connection.groupUsersUpdateMessageStream.subscribe((message) => {
|
||||
// TODO: how else can we deduce our current group?
|
||||
currentPlayerGroupIdStore.set(message.groupId);
|
||||
});
|
||||
|
||||
/**
|
||||
* Triggered when we receive the JWT token to connect to Jitsi
|
||||
*/
|
||||
@@ -868,7 +890,7 @@ export class GameScene extends DirtyScene {
|
||||
);
|
||||
|
||||
//this.initUsersPosition(roomJoinedMessage.users);
|
||||
this.connectionAnswerPromiseResolve(onConnect.room);
|
||||
this.connectionAnswerPromiseDeferred.resolve(onConnect.room);
|
||||
// Analyze tags to find if we are admin. If yes, show console.
|
||||
|
||||
if (this.scene.isSleeping()) {
|
||||
@@ -959,7 +981,9 @@ export class GameScene extends DirtyScene {
|
||||
context.arc(48, 48, 48, 0, 2 * Math.PI, false);
|
||||
// context.lineWidth = 5;
|
||||
context.strokeStyle = "#ffffff";
|
||||
context.fillStyle = "#ffffff44";
|
||||
context.stroke();
|
||||
context.fill();
|
||||
this.circleTexture.refresh();
|
||||
|
||||
//create red circle canvas use to create sprite
|
||||
@@ -969,7 +993,9 @@ export class GameScene extends DirtyScene {
|
||||
contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false);
|
||||
//context.lineWidth = 5;
|
||||
contextRed.strokeStyle = "#ff0000";
|
||||
contextRed.fillStyle = "#ff000044";
|
||||
contextRed.stroke();
|
||||
contextRed.fill();
|
||||
this.circleRedTexture.refresh();
|
||||
}
|
||||
|
||||
@@ -1283,7 +1309,7 @@ ${escapedMessage}
|
||||
iframeListener.registerAnswerer("getState", async () => {
|
||||
// The sharedVariablesManager is not instantiated before the connection is established. So we need to wait
|
||||
// for the connection to send back the answer.
|
||||
await this.connectionAnswerPromise;
|
||||
await this.connectionAnswerPromiseDeferred.promise;
|
||||
return {
|
||||
mapUrl: this.MapUrlFile,
|
||||
startLayerName: this.startPositionCalculator.startLayerName,
|
||||
@@ -1306,7 +1332,7 @@ ${escapedMessage}
|
||||
})
|
||||
);
|
||||
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
|
||||
return this.connectionAnswerPromise.then(() => {
|
||||
return this.connectionAnswerPromiseDeferred.promise.then(() => {
|
||||
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
|
||||
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
|
||||
let newFirstgid = 1;
|
||||
@@ -1839,12 +1865,15 @@ ${escapedMessage}
|
||||
case "GroupCreatedUpdatedEvent":
|
||||
this.doShareGroupPosition(event.event);
|
||||
break;
|
||||
case "DeleteGroupEvent":
|
||||
this.doDeleteGroup(event.groupId);
|
||||
break;
|
||||
case "PlayerDetailsUpdated":
|
||||
this.doUpdatePlayerDetails(event.details);
|
||||
break;
|
||||
case "DeleteGroupEvent": {
|
||||
this.doDeleteGroup(event.groupId);
|
||||
currentPlayerGroupIdStore.set(undefined);
|
||||
currentPlayerGroupLockStateStore.set(undefined);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const tmp: never = event;
|
||||
}
|
||||
@@ -1907,7 +1936,7 @@ ${escapedMessage}
|
||||
return;
|
||||
}
|
||||
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.superLoad, addPlayerData.characterLayers);
|
||||
const player = new RemotePlayer(
|
||||
addPlayerData.userId,
|
||||
addPlayerData.userUuid,
|
||||
@@ -2018,11 +2047,16 @@ ${escapedMessage}
|
||||
this,
|
||||
Math.round(groupPositionMessage.position.x),
|
||||
Math.round(groupPositionMessage.position.y),
|
||||
groupPositionMessage.groupSize === MAX_PER_GROUP ? "circleSprite-red" : "circleSprite-white"
|
||||
groupPositionMessage.groupSize === MAX_PER_GROUP || groupPositionMessage.locked
|
||||
? "circleSprite-red"
|
||||
: "circleSprite-white"
|
||||
);
|
||||
sprite.setDisplayOrigin(48, 48);
|
||||
this.add.existing(sprite);
|
||||
this.groups.set(groupPositionMessage.groupId, sprite);
|
||||
if (this.currentPlayerGroupId === groupPositionMessage.groupId) {
|
||||
currentPlayerGroupLockStateStore.set(groupPositionMessage.locked);
|
||||
}
|
||||
return sprite;
|
||||
}
|
||||
|
||||
@@ -2078,8 +2112,6 @@ ${escapedMessage}
|
||||
right: camera.scrollX + camera.width,
|
||||
bottom: camera.scrollY + camera.height,
|
||||
});
|
||||
|
||||
this.loader.resize();
|
||||
}
|
||||
|
||||
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
|
||||
|
||||
@@ -3,39 +3,16 @@ import { BodyResourceDescriptionInterface, PlayerTexturesKey } from "../Entity/P
|
||||
import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import { PlayerTextures } from "../Entity/PlayerTextures";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
protected playerTextures: PlayerTextures;
|
||||
protected superLoad: SuperLoaderPlugin;
|
||||
|
||||
constructor(params: { key: string }) {
|
||||
super(params);
|
||||
this.playerTextures = new PlayerTextures();
|
||||
}
|
||||
|
||||
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.playerTextures.getTexturesResources(PlayerTexturesKey.Woka);
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of Object.values(textures)) {
|
||||
if (texture.level === -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadWokaTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
for (const textures of this.playerTextures.getLayers()) {
|
||||
for (const texture of Object.values(textures)) {
|
||||
if (texture.level !== -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadWokaTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
this.superLoad = new SuperLoaderPlugin(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { get } from "svelte/store";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
@@ -41,45 +42,30 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
preload() {
|
||||
const wokaMetadataKey = "woka-list";
|
||||
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
|
||||
this.cache.json.remove(wokaMetadataKey);
|
||||
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
|
||||
this.load.json(
|
||||
wokaMetadataKey,
|
||||
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
|
||||
undefined,
|
||||
{
|
||||
responseType: "text",
|
||||
headers: {
|
||||
Authorization: localUserStore.getAuthToken() ?? "",
|
||||
this.superLoad
|
||||
.json(
|
||||
wokaMetadataKey,
|
||||
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
|
||||
undefined,
|
||||
{
|
||||
responseType: "text",
|
||||
headers: {
|
||||
Authorization: localUserStore.getAuthToken() ?? "",
|
||||
},
|
||||
withCredentials: true,
|
||||
},
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
|
||||
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
|
||||
this.loadCustomSceneSelectCharacters()
|
||||
.then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if (
|
||||
bodyResourceDescription.level == undefined ||
|
||||
bodyResourceDescription.level < 0 ||
|
||||
bodyResourceDescription.level > 5
|
||||
) {
|
||||
throw new Error("Texture level is null");
|
||||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
(key, type, data) => {
|
||||
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
|
||||
|
||||
this.layers = loadAllLayers(this.load, this.playerTextures);
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
});
|
||||
this.layers = loadAllLayers(this.load, this.playerTextures);
|
||||
this.lazyloadingAttempt = false;
|
||||
}
|
||||
)
|
||||
.catch((e) => console.error(e));
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Scene } from "phaser";
|
||||
|
||||
export const EmptySceneName = "EmptyScene";
|
||||
|
||||
export class EmptyScene extends Scene {
|
||||
constructor() {
|
||||
super({
|
||||
key: EmptySceneName,
|
||||
});
|
||||
}
|
||||
|
||||
preload() {}
|
||||
|
||||
create() {}
|
||||
|
||||
update(time: number, delta: number): void {}
|
||||
}
|
||||
@@ -28,7 +28,10 @@ export class LoginScene extends ResizableScene {
|
||||
gameManager.currentStartedRoom &&
|
||||
gameManager.currentStartedRoom.authenticationMandatory
|
||||
) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
const redirect = connectionManager.loadOpenIDScreen();
|
||||
if (redirect !== null) {
|
||||
window.location.assign(redirect.toString());
|
||||
}
|
||||
loginSceneVisibleIframeStore.set(true);
|
||||
}
|
||||
loginSceneVisibleStore.set(true);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
|
||||
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
@@ -44,38 +45,31 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
preload() {
|
||||
const wokaMetadataKey = "woka-list";
|
||||
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
|
||||
this.cache.json.remove(wokaMetadataKey);
|
||||
|
||||
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
|
||||
this.load.json(
|
||||
wokaMetadataKey,
|
||||
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
|
||||
undefined,
|
||||
{
|
||||
responseType: "text",
|
||||
headers: {
|
||||
Authorization: localUserStore.getAuthToken() ?? "",
|
||||
this.superLoad
|
||||
.json(
|
||||
wokaMetadataKey,
|
||||
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
|
||||
undefined,
|
||||
{
|
||||
responseType: "text",
|
||||
headers: {
|
||||
Authorization: localUserStore.getAuthToken() ?? "",
|
||||
},
|
||||
withCredentials: true,
|
||||
},
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
|
||||
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
|
||||
this.loadSelectSceneCharacters()
|
||||
.then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
|
||||
this.lazyloadingAttempt = false;
|
||||
(key, type, data) => {
|
||||
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
|
||||
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
|
||||
this.lazyloadingAttempt = false;
|
||||
}
|
||||
)
|
||||
.catch((e) => console.error(e));
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
});
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { Scene } from "phaser";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
|
||||
/**
|
||||
* A wrapper around Phaser LoaderPlugin. Each method returns a (cancelable) Promise that resolves as soon as
|
||||
* the file is loaded.
|
||||
*
|
||||
* As a bonus, if the scene is destroyed BEFORE the promise resolves, the promise is canceled automatically.
|
||||
* So there is no risk of trying to add a resource on a closed scene.
|
||||
*/
|
||||
export class SuperLoaderPlugin {
|
||||
constructor(private scene: Scene) {}
|
||||
|
||||
public spritesheet(
|
||||
key: string,
|
||||
url: string,
|
||||
frameConfig?: Phaser.Types.Loader.FileTypes.ImageFrameConfig,
|
||||
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject
|
||||
) {
|
||||
return this.loadResource<Texture>(
|
||||
() => {
|
||||
this.scene.load.spritesheet(key, url, frameConfig, xhrSettings);
|
||||
},
|
||||
key,
|
||||
url,
|
||||
() => {
|
||||
if (this.scene.load.textureManager.exists(key)) {
|
||||
return this.scene.load.textureManager.get(key);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
"spritesheet"
|
||||
);
|
||||
}
|
||||
|
||||
public image(key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
|
||||
return this.loadResource<Texture>(
|
||||
() => {
|
||||
this.scene.load.image(key, url, xhrSettings);
|
||||
},
|
||||
key,
|
||||
url,
|
||||
() => {
|
||||
if (this.scene.load.textureManager.exists(key)) {
|
||||
return this.scene.load.textureManager.get(key);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
"image"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param url
|
||||
* @param dataKey
|
||||
* @param xhrSettings
|
||||
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
|
||||
*/
|
||||
public json(
|
||||
key: string,
|
||||
url: string,
|
||||
dataKey?: string,
|
||||
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject,
|
||||
immediateCallback?: (key: string, type: string, data: unknown) => void
|
||||
) {
|
||||
return this.loadResource<unknown>(
|
||||
() => {
|
||||
this.scene.load.json(key, url, dataKey, xhrSettings);
|
||||
},
|
||||
key,
|
||||
url,
|
||||
() => {
|
||||
if (this.scene.load.cacheManager.json.exists(key)) {
|
||||
return this.scene.load.cacheManager.json.get(key);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
"json",
|
||||
immediateCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callback The function that calls the loader to load a resource
|
||||
* @param key The key of the resource to be loaded
|
||||
* @param fromCache A function that checks in the cache if the resource is already available
|
||||
* @param type The type of resource loaded
|
||||
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
|
||||
* @private
|
||||
*/
|
||||
private loadResource<T>(
|
||||
callback: () => void,
|
||||
key: string,
|
||||
url: string,
|
||||
fromCache: () => T | undefined,
|
||||
type: string,
|
||||
immediateCallback?: (key: string, type: string, data: unknown) => void
|
||||
): CancelablePromise<T> {
|
||||
// If for some reason, the "url" is empty, let's reject the promise.
|
||||
if (!url) {
|
||||
console.error("Tried to load an empty " + type + ". URL is missing.");
|
||||
return CancelablePromise.reject(Error("Failed loading " + type + ": URL is empty"));
|
||||
}
|
||||
|
||||
return new CancelablePromise<T>((res, rej, cancel) => {
|
||||
if (this.scene.scene.settings.status === Phaser.Scenes.DESTROYED) {
|
||||
rej(new Error("Trying to load a " + type + " in a Scene that is already destroyed."));
|
||||
}
|
||||
|
||||
const resource = fromCache();
|
||||
if (resource !== undefined) {
|
||||
return res(resource);
|
||||
}
|
||||
|
||||
let destroySceneEventRegistered = false;
|
||||
|
||||
const unloadCallbacks = () => {
|
||||
this.scene.load.off("filecomplete-" + type + "-" + key, successCallback);
|
||||
this.scene.load.off("loaderror", errorCallback);
|
||||
if (destroySceneEventRegistered) {
|
||||
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
|
||||
}
|
||||
};
|
||||
|
||||
const errorCallback = (file: { src: string }) => {
|
||||
if (file.src !== url) return;
|
||||
console.error("Failed loading " + type + ": ", url);
|
||||
rej(new Error('Failed loading "+type+": "' + url + '"'));
|
||||
unloadCallbacks();
|
||||
};
|
||||
|
||||
const successCallback = (key: string, type: string, data: unknown) => {
|
||||
this.scene.load.off("loaderror", errorCallback);
|
||||
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
|
||||
const resource = fromCache();
|
||||
if (!resource) {
|
||||
return rej(new Error("Newly loaded resource not available in cache"));
|
||||
}
|
||||
res(resource);
|
||||
|
||||
// The "then" callbacks registered on the promise are not executed immediately when "res" is called.
|
||||
// Instead, they are called after the current JS thread finishes.
|
||||
// In some cases, we want the callbacks to execute right now. In particular if we load a map / json file
|
||||
// that contains references to other files that needs to be loaded.
|
||||
if (immediateCallback) {
|
||||
immediateCallback(key, type, data);
|
||||
}
|
||||
console.log("Resolve done for ", url);
|
||||
};
|
||||
|
||||
cancel(() => {
|
||||
unloadCallbacks();
|
||||
});
|
||||
|
||||
callback();
|
||||
|
||||
this.scene.load.once("filecomplete-" + type + "-" + key, successCallback);
|
||||
this.scene.load.on("loaderror", errorCallback);
|
||||
if (this.scene.scene.settings.status > Phaser.Scenes.LOADING) {
|
||||
// When the scene is destroyed, let's remove our callbacks.
|
||||
// We only need to register this destroy event is the scene is not in loading state (otherwise, Phaser
|
||||
// will take care of that for us).
|
||||
destroySceneEventRegistered = true;
|
||||
this.scene.events.once(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
|
||||
|
||||
// Let's start the loader if we are no more in the scene loading state
|
||||
this.scene.load.start();
|
||||
// Due to a bug, if the loader is already started, additional items are not added.... unless we
|
||||
// explicitly call the "update" method.
|
||||
this.scene.load.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const currentPlayerGroupIdStore = writable<number | undefined>(undefined);
|
||||
export const currentPlayerGroupLockStateStore = writable<boolean | undefined>(undefined);
|
||||
@@ -11,7 +11,7 @@ import { peerStore } from "./PeerStore";
|
||||
import { privacyShutdownStore } from "./PrivacyShutdownStore";
|
||||
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
|
||||
import { SoundMeter } from "../Phaser/Components/SoundMeter";
|
||||
import { AudioContext } from "standardized-audio-context";
|
||||
import { visibilityStore } from "./VisibilityStore";
|
||||
|
||||
/**
|
||||
* A store that contains the camera state requested by the user (on or off).
|
||||
@@ -242,6 +242,7 @@ export const mediaStreamConstraintsStore = derived(
|
||||
privacyShutdownStore,
|
||||
cameraEnergySavingStore,
|
||||
isSilentStore,
|
||||
visibilityStore,
|
||||
],
|
||||
(
|
||||
[
|
||||
@@ -254,6 +255,7 @@ export const mediaStreamConstraintsStore = derived(
|
||||
$privacyShutdownStore,
|
||||
$cameraEnergySavingStore,
|
||||
$isSilentStore,
|
||||
$visibilityStore,
|
||||
],
|
||||
set
|
||||
) => {
|
||||
@@ -292,7 +294,14 @@ export const mediaStreamConstraintsStore = derived(
|
||||
|
||||
// Disable webcam for privacy reasons (the game is not visible and we were talking to no one)
|
||||
if ($privacyShutdownStore === true) {
|
||||
currentVideoConstraint = false;
|
||||
const userMicrophonePrivacySetting = localUserStore.getMicrophonePrivacySettings();
|
||||
const userCameraPrivacySetting = localUserStore.getCameraPrivacySettings();
|
||||
if (!userMicrophonePrivacySetting) {
|
||||
currentAudioConstraint = false;
|
||||
}
|
||||
if (!userCameraPrivacySetting) {
|
||||
currentVideoConstraint = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable webcam for energy reasons (the user is not moving and we are talking to no one)
|
||||
@@ -545,8 +554,12 @@ export const obtainedMediaConstraintStore = derived<Readable<MediaStreamConstrai
|
||||
|
||||
export const localVolumeStore = readable<number | undefined>(undefined, (set) => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
let soundMeter: SoundMeter;
|
||||
const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => {
|
||||
clearInterval(timeout);
|
||||
if (soundMeter) {
|
||||
soundMeter.stop();
|
||||
}
|
||||
if (localStreamStoreValue.type === "error") {
|
||||
set(undefined);
|
||||
return;
|
||||
@@ -557,7 +570,7 @@ export const localVolumeStore = readable<number | undefined>(undefined, (set) =>
|
||||
set(undefined);
|
||||
return;
|
||||
}
|
||||
const soundMeter = new SoundMeter(mediaStream);
|
||||
soundMeter = new SoundMeter(mediaStream);
|
||||
let error = false;
|
||||
|
||||
timeout = setInterval(() => {
|
||||
@@ -575,6 +588,9 @@ export const localVolumeStore = readable<number | undefined>(undefined, (set) =>
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearInterval(timeout);
|
||||
if (soundMeter) {
|
||||
soundMeter.stop();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -619,7 +619,10 @@ class CoWebsiteManager {
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
}, animationTime);
|
||||
} else if (!highlightedEmbed) {
|
||||
} else if (
|
||||
!highlightedEmbed &&
|
||||
this.getCoWebsites().find((searchCoWebsite) => searchCoWebsite.getId() === coWebsite.getId())
|
||||
) {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: coWebsite,
|
||||
|
||||
@@ -197,7 +197,10 @@ class JitsiFactory {
|
||||
|
||||
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
|
||||
this.jitsiApi.executeCommand("displayName", playerName);
|
||||
|
||||
this.jitsiApi.addListener("videoConferenceJoined", () => {
|
||||
this.jitsiApi?.executeCommand("displayName", playerName);
|
||||
});
|
||||
|
||||
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
|
||||
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
|
||||
|
||||
@@ -74,12 +74,17 @@ export class VideoPeer extends Peer {
|
||||
|
||||
this.volumeStore = readable<number | undefined>(undefined, (set) => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
let soundMeter: SoundMeter;
|
||||
const unsubscribe = this.streamStore.subscribe((mediaStream) => {
|
||||
clearInterval(timeout);
|
||||
if (soundMeter) {
|
||||
soundMeter.stop();
|
||||
}
|
||||
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
|
||||
set(undefined);
|
||||
return;
|
||||
}
|
||||
const soundMeter = new SoundMeter(mediaStream);
|
||||
soundMeter = new SoundMeter(mediaStream);
|
||||
let error = false;
|
||||
|
||||
timeout = setInterval(() => {
|
||||
@@ -97,6 +102,9 @@ export class VideoPeer extends Peer {
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearInterval(timeout);
|
||||
if (soundMeter) {
|
||||
soundMeter.stop();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { Translation } from "../i18n-types";
|
||||
|
||||
const audio: NonNullable<Translation["audio"]> = {
|
||||
manager: {
|
||||
reduce: "Während Unterhaltungen verringern",
|
||||
reduce: "Verringern Sie die Lautstärke des Audioplayers während des Sprechens",
|
||||
allow: "Ton zulassen",
|
||||
},
|
||||
message: "Sprachnachricht",
|
||||
};
|
||||
|
||||
@@ -13,6 +13,10 @@ const camera: NonNullable<Translation["camera"]> = {
|
||||
'Bitte klicke auf "Diese Entscheidungen speichern" Schaltfläche um erneute Nachfragen nach der Freigabe in Firefox zu verhindern.',
|
||||
refresh: "Aktualisieren",
|
||||
continue: "Ohne Kamera fortfahren",
|
||||
screen: {
|
||||
firefox: "/resources/help-setting-camera-permission/de-DE-chrome.png",
|
||||
chrome: "/resources/help-setting-camera-permission/de-DE-chrome.png",
|
||||
},
|
||||
},
|
||||
my: {
|
||||
silentZone: "Stiller Bereich",
|
||||
|
||||
@@ -57,6 +57,13 @@ const menu: NonNullable<Translation["menu"]> = {
|
||||
language: {
|
||||
title: "Sprache",
|
||||
},
|
||||
privacySettings: {
|
||||
title: "Einstellungen Abwesenheitsmodus",
|
||||
explanation:
|
||||
"Falls der WorkAdventure Tab nicht aktiv ist wird in den Abwesenheitsmodus umgeschaltet. Für diesen Modus kann eingestellt werden, ob die Kamera und/oder das Mikrofon deaktiviert sind solange der Tab nicht sichtbar ist.",
|
||||
cameraToggle: "Kamera",
|
||||
microphoneToggle: "Mikrofon",
|
||||
},
|
||||
save: {
|
||||
warning: "(Das Spiel wird nach dem Speichern neugestartet)",
|
||||
button: "Speichern",
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { BaseTranslation } from "../i18n-types";
|
||||
|
||||
const audio: BaseTranslation = {
|
||||
manager: {
|
||||
reduce: "reduce in conversations",
|
||||
reduce: "Decrease audio player volume while speaking",
|
||||
allow: "Allow audio",
|
||||
},
|
||||
message: "Audio message",
|
||||
};
|
||||
|
||||
@@ -13,6 +13,10 @@ const camera: BaseTranslation = {
|
||||
'Please click the "Remember this decision" checkbox, if you don\'t want Firefox to keep asking you the authorization.',
|
||||
refresh: "Refresh",
|
||||
continue: "Continue without webcam",
|
||||
screen: {
|
||||
firefox: "/resources/help-setting-camera-permission/en-US-firefox.png",
|
||||
chrome: "/resources/help-setting-camera-permission/en-US-firefox.png",
|
||||
},
|
||||
},
|
||||
my: {
|
||||
silentZone: "Silent zone",
|
||||
|
||||
@@ -57,6 +57,13 @@ const menu: BaseTranslation = {
|
||||
language: {
|
||||
title: "Language",
|
||||
},
|
||||
privacySettings: {
|
||||
title: "Away mode settings",
|
||||
explanation:
|
||||
'When the WorkAdventure tab is not visible, it switches to "away mode". In this mode, you can decide to automatically disable your webcam and/or microphone for as long as the tab stays hidden.',
|
||||
cameraToggle: "Camera",
|
||||
microphoneToggle: "Microphone",
|
||||
},
|
||||
save: {
|
||||
warning: "(Saving these settings will restart the game)",
|
||||
button: "Save",
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { Translation } from "../i18n-types";
|
||||
|
||||
const audio: NonNullable<Translation["audio"]> = {
|
||||
manager: {
|
||||
reduce: "réduit dans les conversations",
|
||||
reduce: "Diminuer le volume du lecteur audio dans les conversations",
|
||||
allow: "Autoriser l'audio",
|
||||
},
|
||||
message: "Message audio",
|
||||
};
|
||||
|
||||
@@ -13,6 +13,10 @@ const camera: NonNullable<Translation["camera"]> = {
|
||||
'Veuillez cocher la case "Se souvenir de cette décision" si vous ne voulez pas que Firefox vous demande sans cesse l\'autorisation.',
|
||||
refresh: "Rafraîchir",
|
||||
continue: "Continuer sans webcam",
|
||||
screen: {
|
||||
firefox: "/resources/help-setting-camera-permission/fr-FR-chrome.png",
|
||||
chrome: "/resources/help-setting-camera-permission/fr-FR-chrome.png",
|
||||
},
|
||||
},
|
||||
my: {
|
||||
silentZone: "Zone silencieuse",
|
||||
|
||||
@@ -57,6 +57,13 @@ const menu: NonNullable<Translation["menu"]> = {
|
||||
language: {
|
||||
title: "Langage",
|
||||
},
|
||||
privacySettings: {
|
||||
title: "Paramètres du mode absent",
|
||||
explanation:
|
||||
"Quand l'onglet WorkAdventure n'est pas visible, vous passez en \"mode absent\". Lorsque ce mode est actif, vous pouvez décider de garder vos webcam et/ou micro désactivés tant que vous ne revenez pas sur l'onglet",
|
||||
cameraToggle: "Camera",
|
||||
microphoneToggle: "Microphone",
|
||||
},
|
||||
save: {
|
||||
warning: "(La sauvegarde de ces paramètres redémarre le jeu)",
|
||||
button: "Sauvegarder",
|
||||
|
||||
@@ -21,7 +21,6 @@ import type { Popup } from "./Api/iframe/Ui/Popup";
|
||||
import type { Sound } from "./Api/iframe/Sound/Sound";
|
||||
import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution";
|
||||
import camera from "./Api/iframe/camera";
|
||||
import {} from "./window";
|
||||
|
||||
const globalState = createState("global");
|
||||
|
||||
@@ -183,6 +182,13 @@ const wa = {
|
||||
|
||||
export type WorkAdventureApi = typeof wa;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
WA: WorkAdventureApi;
|
||||
}
|
||||
let WA: WorkAdventureApi;
|
||||
}
|
||||
|
||||
window.WA = wa;
|
||||
|
||||
window.addEventListener(
|
||||
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
import { WorkAdventureApi } from "./iframe_api";
|
||||
import { WorkAdventureDesktopApi } from "@wa-preload-app";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
WA: WorkAdventureApi;
|
||||
WAD: WorkAdventureDesktopApi;
|
||||
}
|
||||
let WA: WorkAdventureApi;
|
||||
}
|
||||
Reference in New Issue
Block a user