merged develop

This commit is contained in:
Piotr 'pwh' Hanusiak
2022-03-28 13:16:29 +02:00
284 changed files with 13296 additions and 3161 deletions
@@ -0,0 +1,12 @@
import * as tg from "generic-type-guard";
export const isActionsMenuActionClickedEvent = new tg.IsInterface()
.withProperties({
id: tg.isNumber,
actionName: tg.isString,
})
.get();
export type ActionsMenuActionClickedEvent = tg.GuardedType<typeof isActionsMenuActionClickedEvent>;
export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void;
@@ -0,0 +1,12 @@
import * as tg from "generic-type-guard";
export const isAddActionsMenuKeyToRemotePlayerEvent = new tg.IsInterface()
.withProperties({
id: tg.isNumber,
actionKey: tg.isString,
})
.get();
export type AddActionsMenuKeyToRemotePlayerEvent = tg.GuardedType<typeof isAddActionsMenuKeyToRemotePlayerEvent>;
export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void;
+9
View File
@@ -36,6 +36,10 @@ import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
import { isColorEvent } from "./ColorEvent";
import { isMovePlayerToEventConfig } from "./MovePlayerToEvent";
import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer";
import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent";
import type { AddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent";
import type { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent";
export interface TypedMessageEvent<T> extends MessageEvent {
data: T;
@@ -45,6 +49,8 @@ export interface TypedMessageEvent<T> extends MessageEvent {
* List event types sent from an iFrame to WorkAdventure
*/
export type IframeEventMap = {
addActionsMenuKeyToRemotePlayer: AddActionsMenuKeyToRemotePlayerEvent;
removeActionsMenuKeyFromRemotePlayer: RemoveActionsMenuKeyFromRemotePlayerEvent;
loadPage: LoadPageEvent;
chat: ChatEvent;
cameraFollowPlayer: CameraFollowPlayerEvent;
@@ -58,6 +64,7 @@ export type IframeEventMap = {
displayBubble: null;
removeBubble: null;
onPlayerMove: undefined;
onOpenActionMenu: undefined;
onCameraUpdate: undefined;
showLayer: LayerEvent;
hideLayer: LayerEvent;
@@ -90,6 +97,8 @@ export interface IframeResponseEventMap {
enterZoneEvent: ChangeZoneEvent;
leaveZoneEvent: ChangeZoneEvent;
buttonClickedEvent: ButtonClickedEvent;
remotePlayerClickedEvent: RemotePlayerClickedEvent;
actionsMenuActionClickedEvent: ActionsMenuActionClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent;
wasCameraUpdated: WasCameraUpdatedEvent;
menuItemClicked: MenuItemClickedEvent;
@@ -0,0 +1,15 @@
import * as tg from "generic-type-guard";
// TODO: Change for player Clicked, add all neccessary data
export const isRemotePlayerClickedEvent = new tg.IsInterface()
.withProperties({
id: tg.isNumber,
})
.get();
/**
* A message sent from the game to the iFrame when RemotePlayer is clicked.
*/
export type RemotePlayerClickedEvent = tg.GuardedType<typeof isRemotePlayerClickedEvent>;
export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void;
@@ -0,0 +1,16 @@
import * as tg from "generic-type-guard";
export const isRemoveActionsMenuKeyFromRemotePlayerEvent = new tg.IsInterface()
.withProperties({
id: tg.isNumber,
actionKey: tg.isString,
})
.get();
export type RemoveActionsMenuKeyFromRemotePlayerEvent = tg.GuardedType<
typeof isRemoveActionsMenuKeyFromRemotePlayerEvent
>;
export type RemoveActionsMenuKeyFromRemotePlayerEventCallback = (
event: RemoveActionsMenuKeyFromRemotePlayerEvent
) => void;
+43
View File
@@ -34,6 +34,16 @@ import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent";
import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent";
import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent";
import {
AddActionsMenuKeyToRemotePlayerEvent,
isAddActionsMenuKeyToRemotePlayerEvent,
} from "./Events/AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent";
import {
isRemoveActionsMenuKeyFromRemotePlayerEvent,
RemoveActionsMenuKeyFromRemotePlayerEvent,
} from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent";
type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"],
@@ -63,6 +73,15 @@ class IframeListener {
private readonly _cameraFollowPlayerStream: Subject<CameraFollowPlayerEvent> = new Subject();
public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable();
private readonly _addActionsMenuKeyToRemotePlayerStream: Subject<AddActionsMenuKeyToRemotePlayerEvent> =
new Subject();
public readonly addActionsMenuKeyToRemotePlayerStream = this._addActionsMenuKeyToRemotePlayerStream.asObservable();
private readonly _removeActionsMenuKeyFromRemotePlayerEvent: Subject<RemoveActionsMenuKeyFromRemotePlayerEvent> =
new Subject();
public readonly removeActionsMenuKeyFromRemotePlayerEvent =
this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable();
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
@@ -241,6 +260,16 @@ class IframeListener {
this._removeBubbleStream.next();
} else if (payload.type == "onPlayerMove") {
this.sendPlayerMove = true;
} else if (
payload.type == "addActionsMenuKeyToRemotePlayer" &&
isAddActionsMenuKeyToRemotePlayerEvent(payload.data)
) {
this._addActionsMenuKeyToRemotePlayerStream.next(payload.data);
} else if (
payload.type == "removeActionsMenuKeyFromRemotePlayer" &&
isRemoveActionsMenuKeyFromRemotePlayerEvent(payload.data)
) {
this._removeActionsMenuKeyFromRemotePlayerEvent.next(payload.data);
} else if (payload.type == "onCameraUpdate") {
this._trackCameraUpdateStream.next();
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
@@ -439,6 +468,20 @@ class IframeListener {
}
}
sendRemotePlayerClickedEvent(event: RemotePlayerClickedEvent) {
this.postMessage({
type: "remotePlayerClickedEvent",
data: event,
});
}
sendActionsMenuActionClickedEvent(event: ActionsMenuActionClickedEvent) {
this.postMessage({
type: "actionsMenuActionClickedEvent",
data: event,
});
}
sendCameraUpdated(event: WasCameraUpdatedEvent) {
this.postMessage({
type: "wasCameraUpdated",
+45
View File
@@ -0,0 +1,45 @@
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;
init() {
if (!window?.WAD?.desktop) {
return;
}
console.log("Yipee you are using the desktop app ;)");
window.WAD.onMuteToggle(() => {
if (this.isSilent) return;
if (get(requestedMicrophoneState) === true) {
requestedMicrophoneState.disableMicrophone();
} else {
requestedMicrophoneState.enableMicrophone();
}
});
window.WAD.onCameraToggle(() => {
if (this.isSilent) return;
if (get(requestedCameraState) === true) {
requestedCameraState.disableWebcam();
} else {
requestedCameraState.enableWebcam();
}
});
isSilentStore.subscribe((value) => {
this.isSilent = value;
});
}
}
export const desktopApi = new DesktopApi();
+113 -5
View File
@@ -8,6 +8,12 @@ import { ActionMessage } from "./Ui/ActionMessage";
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
import { Menu } from "./Ui/Menu";
import type { RequireOnlyOne } from "../types";
import { isRemotePlayerClickedEvent, RemotePlayerClickedEvent } from "../Events/RemotePlayerClickedEvent";
import {
ActionsMenuActionClickedEvent,
isActionsMenuActionClickedEvent,
} from "../Events/ActionsMenuActionClickedEvent";
import { Observable, Subject } from "rxjs";
let popupId = 0;
const popups: Map<number, Popup> = new Map<number, Popup>();
@@ -42,7 +48,77 @@ export interface ActionMessageOptions {
callback: () => void;
}
export interface RemotePlayerInterface {
addAction(key: string, callback: Function): void;
}
export class RemotePlayer implements RemotePlayerInterface {
private id: number;
private actions: Map<string, ActionsMenuAction> = new Map<string, ActionsMenuAction>();
constructor(id: number) {
this.id = id;
}
public addAction(key: string, callback: Function): ActionsMenuAction {
const newAction = new ActionsMenuAction(this, key, callback);
this.actions.set(key, newAction);
sendToWorkadventure({
type: "addActionsMenuKeyToRemotePlayer",
data: { id: this.id, actionKey: key },
});
return newAction;
}
public callAction(key: string): void {
const action = this.actions.get(key);
if (action) {
action.call();
}
}
public removeAction(key: string): void {
this.actions.delete(key);
sendToWorkadventure({
type: "removeActionsMenuKeyFromRemotePlayer",
data: { id: this.id, actionKey: key },
});
}
}
export class ActionsMenuAction {
private remotePlayer: RemotePlayer;
private key: string;
private callback: Function;
constructor(remotePlayer: RemotePlayer, key: string, callback: Function) {
this.remotePlayer = remotePlayer;
this.key = key;
this.callback = callback;
}
public call(): void {
this.callback();
}
public remove(): void {
this.remotePlayer.removeAction(this.key);
}
}
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
public readonly _onRemotePlayerClicked: Subject<RemotePlayerInterface>;
public readonly onRemotePlayerClicked: Observable<RemotePlayerInterface>;
private currentlyClickedRemotePlayer?: RemotePlayer;
constructor() {
super();
this._onRemotePlayerClicked = new Subject<RemotePlayerInterface>();
this.onRemotePlayerClicked = this._onRemotePlayerClicked.asObservable();
}
callbacks = [
apiCallback({
type: "buttonClickedEvent",
@@ -82,9 +158,38 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
}
},
}),
apiCallback({
type: "remotePlayerClickedEvent",
typeChecker: isRemotePlayerClickedEvent,
callback: (payloadData: RemotePlayerClickedEvent) => {
this.currentlyClickedRemotePlayer = new RemotePlayer(payloadData.id);
this._onRemotePlayerClicked.next(this.currentlyClickedRemotePlayer);
},
}),
apiCallback({
type: "actionsMenuActionClickedEvent",
typeChecker: isActionsMenuActionClickedEvent,
callback: (payloadData: ActionsMenuActionClickedEvent) => {
this.currentlyClickedRemotePlayer?.callAction(payloadData.actionName);
},
}),
];
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
public addActionsMenuKeyToRemotePlayer(id: number, actionKey: string): void {
sendToWorkadventure({
type: "addActionsMenuKeyToRemotePlayer",
data: { id, actionKey },
});
}
public removeActionsMenuKeyFromRemotePlayer(id: number, actionKey: string): void {
sendToWorkadventure({
type: "removeActionsMenuKeyFromRemotePlayer",
data: { id, actionKey },
});
}
public openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
popupId++;
const popup = new Popup(popupId);
const btnMap = new Map<number, () => void>();
@@ -119,7 +224,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
return popup;
}
registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu {
public registerMenuCommand(
commandDescriptor: string,
options: MenuOptions | ((commandDescriptor: string) => void)
): Menu {
const menu = new Menu(commandDescriptor);
if (typeof options === "function") {
@@ -168,15 +276,15 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
return menu;
}
displayBubble(): void {
public displayBubble(): void {
sendToWorkadventure({ type: "displayBubble", data: null });
}
removeBubble(): void {
public removeBubble(): void {
sendToWorkadventure({ type: "removeBubble", data: null });
}
displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {
public displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {
const actionMessage = new ActionMessage(actionMessageOptions, () => {
actionMessages.delete(actionMessage.uuid);
});
@@ -2,10 +2,12 @@
import { actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { onDestroy } from "svelte";
import type { ActionsMenuAction } from "../../Stores/ActionsMenuStore";
import type { Unsubscriber } from "svelte/store";
import type { ActionsMenuData } from "../../Stores/ActionsMenuStore";
let actionsMenuData: ActionsMenuData | undefined;
let sortedActions: ActionsMenuAction[] | undefined;
let actionsMenuStoreUnsubscriber: Unsubscriber | null;
@@ -21,6 +23,20 @@
actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => {
actionsMenuData = value;
if (actionsMenuData) {
sortedActions = [...actionsMenuData.actions.values()].sort((a, b) => {
const ap = a.priority ?? 0;
const bp = b.priority ?? 0;
if (ap > bp) {
return -1;
}
if (ap < bp) {
return 1;
} else {
return 0;
}
});
}
});
onDestroy(() => {
@@ -37,15 +53,15 @@
<button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>&times</button>
<h2>{actionsMenuData.playerName}</h2>
<div class="actions">
{#each [...actionsMenuData.actions] as { actionName, callback }}
{#each sortedActions ?? [] as action}
<button
type="button"
class="nes-btn"
class="nes-btn {action.style ?? ''}"
on:click|preventDefault={() => {
callback();
action.callback();
}}
>
{actionName}
{action.actionName}
</button>
{/each}
</div>
@@ -61,6 +77,7 @@
height: max-content !important;
max-height: 40vh;
margin-top: 200px;
z-index: 425;
pointer-events: auto;
font-family: "Press Start 2P";
@@ -68,7 +85,7 @@
color: whitesmoke;
.actions {
max-height: calc(100% - 50px);
max-height: 30vh;
width: 100%;
display: block;
overflow-x: hidden;
@@ -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
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

+21
View File
@@ -0,0 +1,21 @@
<!-- https://lihautan.com/notes/svelte-lazy-load/ -->
<script>
export let when = false;
export let component;
let loading;
$: if (when) {
load();
}
function load() {
loading = component();
}
</script>
{#if when}
{#await loading then { default: Component }}
<Component />
{/await}
{/if}
+14 -1
View File
@@ -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>
+7 -8
View File
@@ -12,7 +12,6 @@
import AudioManager from "./AudioManager/AudioManager.svelte";
import CameraControls from "./CameraControls.svelte";
import EmbedScreensContainer from "./EmbedScreens/EmbedScreensContainer.svelte";
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
import LayoutActionManager from "./LayoutActionManager/LayoutActionManager.svelte";
import Menu from "./Menu/Menu.svelte";
@@ -38,6 +37,7 @@
import { LayoutMode } from "../WebRtc/LayoutManager";
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
import Lazy from "./Lazy.svelte";
let mainLayout: HTMLDivElement;
@@ -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,21 +105,19 @@
<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}
{#if $emoteMenuStore}
<EmoteMenu />
{/if}
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
{#if hasEmbedScreen}
<EmbedScreensContainer />
@@ -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

@@ -2,6 +2,7 @@
import type { Game } from "../../Phaser/Game/Game";
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
import LL from "../../i18n/i18n-svelte";
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
export let game: Game;
@@ -40,11 +41,13 @@
class="selectCharacterSceneFormSubmit nes-btn is-primary"
on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()}</button
>
<button
type="submit"
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
>
{#if $customizeAvailableStore}
<button
type="submit"
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
>
{/if}
</section>
</form>
+67 -33
View File
@@ -16,6 +16,10 @@ import { isRegisterData } from "../Messages/JsonMessages/RegisterData";
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
import { limitMapStore } from "../Stores/GameStore";
import { showLimitRoomModalStore } from "../Stores/ModalStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { locales } from "../i18n/i18n-util";
import type { Locales } from "../i18n/i18n-types";
import { setCurrentLocale } from "../i18n/locales";
class ConnectionManager {
private localUser!: LocalUser;
@@ -41,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);
@@ -51,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;
}
@@ -79,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> {
const connexionType = urlManager.getGameConnexionType();
this.connexionType = connexionType;
this._currentRoom = null;
@@ -97,8 +104,9 @@ class ConnectionManager {
if (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 (connexionType === GameConnexionTypes.jwt) {
@@ -120,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);
} else if (connexionType === GameConnexionTypes.register) {
@@ -134,7 +145,7 @@ class ConnectionManager {
console.error("Invalid data received from /register route. Data: ", data);
throw new Error("Invalid data received from /register route.");
}
this.localUser = new LocalUser(data.userUuid, data.textures, data.email);
this.localUser = new LocalUser(data.userUuid, data.email);
this.authToken = data.authToken;
localUserStore.saveUser(this.localUser);
localUserStore.setAuthToken(this.authToken);
@@ -208,28 +219,15 @@ 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;
}
}
}
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
//check if texture was changed
if (this.localUser.textures.length === 0) {
this.localUser.textures = this._currentRoom.textures;
} else {
this._currentRoom.textures.forEach((newTexture) => {
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
return;
}
this.localUser.textures.push(newTexture);
});
}
localUserStore.saveUser(this.localUser);
}
}
if (this._currentRoom == undefined) {
return Promise.reject(new Error("Invalid URL"));
@@ -255,7 +253,7 @@ class ConnectionManager {
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
const data = await axiosWithRetry.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
this.localUser = new LocalUser(data.userUuid, [], data.email);
this.localUser = new LocalUser(data.userUuid, data.email);
this.authToken = data.authToken;
if (!isBenchmark) {
// In benchmark, we don't have a local storage.
@@ -265,7 +263,7 @@ class ConnectionManager {
}
public initBenchmark(): void {
this.localUser = new LocalUser("", []);
this.localUser = new LocalUser("");
}
public connectToRoomSocket(
@@ -342,16 +340,52 @@ class ConnectionManager {
throw new Error("No Auth code provided");
}
}
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
params: { code, nonce, token, playUri: this.currentRoom?.key },
}).then((res) => {
const { authToken, userUuid, email, username, locale, textures } = await Axios.get(
`${PUSHER_URL}/login-callback`,
{
params: { code, nonce, token, playUri: this.currentRoom?.key },
}
).then((res) => {
return res.data;
});
localUserStore.setAuthToken(authToken);
this.localUser = new LocalUser(userUuid, textures, email);
this.localUser = new LocalUser(userUuid, email);
localUserStore.saveUser(this.localUser);
this.authToken = authToken;
if (username) {
gameManager.setPlayerName(username);
}
if (locale) {
try {
if (locales.indexOf(locale) == -1) {
locales.forEach((l) => {
if (l.startsWith(locale.split("-")[0])) {
setCurrentLocale(l);
return;
}
});
} else {
setCurrentLocale(locale as Locales);
}
} catch (err) {
console.warn("Could not set locale", err);
}
}
if (textures) {
const layers: string[] = [];
for (const texture of textures) {
if (texture !== undefined) {
layers.push(texture.id);
}
}
if (layers.length > 0) {
gameManager.setCharacterLayers(layers);
}
}
//user connected, set connected store for menu at true
userIsConnected.set(true);
}
+1 -1
View File
@@ -1,7 +1,6 @@
import type { SignalData } from "simple-peer";
import type { RoomConnection } from "./RoomConnection";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
export interface PointInterface {
x: number;
@@ -89,6 +88,7 @@ export interface RoomJoinedMessageInterface {
//groups: GroupCreatedUpdatedMessageInterface[],
items: { [itemId: number]: unknown };
variables: Map<string, unknown>;
characterLayers: BodyResourceDescriptionInterface[];
}
export interface PlayGlobalMessageInterface {
+10 -11
View File
@@ -1,10 +1,11 @@
import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable";
export type LayerNames = "woka" | "body" | "eyes" | "hair" | "clothes" | "hat" | "accessory";
export interface CharacterTexture {
id: number;
level: number;
id: string;
layer: LayerNames;
url: string;
rights: string;
}
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
@@ -14,9 +15,11 @@ export function isUserNameValid(value: unknown): boolean {
}
export function areCharacterLayersValid(value: string[] | null): boolean {
if (!value || !value.length) return false;
for (let i = 0; i < value.length; i++) {
if (/^\w+$/.exec(value[i]) === null) {
if (!value || !value.length) {
return false;
}
for (const layerName of value) {
if (layerName.length === 0 || layerName === " ") {
return false;
}
}
@@ -24,9 +27,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
}
export class LocalUser {
constructor(
public readonly uuid: string,
public textures: CharacterTexture[],
public email: string | null = null
) {}
constructor(public readonly uuid: string, public email: string | null = null) {}
}
+58
View File
@@ -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++) {
+14 -8
View File
@@ -9,7 +9,7 @@ import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
export class MapDetail {
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
constructor(public readonly mapUrl: string) {}
}
export interface RoomRedirect {
@@ -25,13 +25,14 @@ export class Room {
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
private _mapUrl: string | undefined;
private _textures: CharacterTexture[] | undefined;
private instance: string | undefined;
private readonly _search: URLSearchParams;
private _contactPage: string | undefined;
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;
@@ -118,7 +119,6 @@ export class Room {
} else if (isMapDetailsData(data)) {
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
this._mapUrl = data.mapUrl;
this._textures = data.textures;
this._group = data.group;
this._authenticationMandatory =
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
@@ -128,7 +128,9 @@ export class Room {
this._expireOn = new Date(data.expireOn);
}
this._canReport = data.canReport ?? false;
return new MapDetail(data.mapUrl, data.textures);
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.");
}
@@ -205,10 +207,6 @@ export class Room {
return this.roomUrl.toString();
}
get textures(): CharacterTexture[] | undefined {
return this._textures;
}
get mapUrl(): string {
if (!this._mapUrl) {
throw new Error("Map URL not fetched yet");
@@ -239,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;
}
}
+48 -7
View File
@@ -17,8 +17,8 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe
import { adminMessagesService } from "./AdminMessagesService";
import { connectionManager } from "./ConnectionManager";
import { get } from "svelte/store";
import { warningContainerStore } from "../Stores/MenuStore";
import { followRoleStore, followUsersStore } from "../Stores/FollowStore";
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
import { localUserStore } from "./LocalUserStore";
import {
ServerToClientMessage as ServerToClientMessageTsProto,
@@ -40,8 +40,12 @@ import {
PositionMessage_Direction,
SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto,
PingMessage as PingMessageTsProto,
CharacterLayerMessage,
} from "../Messages/ts-proto-generated/messages";
import { Subject } from "rxjs";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
const manualPingDelay = 20000;
@@ -205,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));
@@ -326,11 +331,30 @@ export class RoomConnection implements RoomConnection {
this.tags = roomJoinedMessage.tag;
this._userRoomToken = roomJoinedMessage.userRoomToken;
// If one of the URLs sent to us does not exist, let's go to the Woka selection screen.
if (
roomJoinedMessage.characterLayer.length !== initCharacterLayers.length ||
roomJoinedMessage.characterLayer.find((layer) => !layer.url)
) {
this.goToSelectYourWokaScene();
this.closed = true;
}
if (this.closed) {
this.closeConnection();
break;
}
const characterLayers = roomJoinedMessage.characterLayer.map(
this.mapCharacterLayerToBodyResourceDescription.bind(this)
);
this._roomJoinedMessageStream.next({
connection: this,
room: {
items,
variables,
characterLayers,
} as RoomJoinedMessageInterface,
});
break;
@@ -340,6 +364,12 @@ export class RoomConnection implements RoomConnection {
this.closed = true;
break;
}
case "invalidTextureMessage": {
this.goToSelectYourWokaScene();
this.closed = true;
break;
}
case "tokenExpiredMessage": {
connectionManager.logout().catch((e) => console.error(e));
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
@@ -584,6 +614,15 @@ export class RoomConnection implements RoomConnection {
});
}*/
private mapCharacterLayerToBodyResourceDescription(
characterLayer: CharacterLayerMessage
): BodyResourceDescriptionInterface {
return {
id: characterLayer.name,
img: characterLayer.url,
};
}
// TODO: move this to protobuf utils
private toMessageUserJoined(message: UserJoinedMessageTsProto): MessageUserJoined {
const position = message.position;
@@ -591,12 +630,7 @@ export class RoomConnection implements RoomConnection {
throw new Error("Invalid JOIN_ROOM message");
}
const characterLayers = message.characterLayers.map((characterLayer): BodyResourceDescriptionInterface => {
return {
name: characterLayer.name,
img: characterLayer.url,
};
});
const characterLayers = message.characterLayers.map(this.mapCharacterLayerToBodyResourceDescription.bind(this));
const companion = message.companion;
@@ -870,4 +904,11 @@ export class RoomConnection implements RoomConnection {
public get userRoomToken(): string | undefined {
return this._userRoomToken;
}
private goToSelectYourWokaScene(): void {
menuVisiblilityStore.set(false);
menuIconVisiblilityStore.set(false);
selectCharacterSceneVisibleStore.set(true);
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
}
}
+71 -39
View File
@@ -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() {
+2 -2
View File
@@ -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);
}
+40 -6
View File
@@ -10,7 +10,7 @@ import type { GameScene } from "../Game/GameScene";
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
import { isSilentStore } from "../../Stores/MediaStore";
import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager";
import { lazyLoadPlayerCharacterTextures } from "./PlayerTexturesLoadingManager";
import { TexturesHelper } from "../Helpers/TexturesHelper";
import type { PictureStore } from "../../Stores/PictureStore";
import { Unsubscriber, Writable, writable } from "svelte/store";
@@ -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,16 +84,35 @@ 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, ["color_22", "eyes_23"]).then((textures) => {
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
});
return lazyLoadPlayerCharacterTextures(scene.superLoad, [
{
id: "color_22",
img: "resources/customisation/character_color/character_color21.png",
},
{
id: "eyes_23",
img: "resources/customisation/character_eyes/character_eyes23.png",
},
])
.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;
@@ -508,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;
}
}
+88 -428
View File
@@ -1,450 +1,110 @@
//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;
}
export interface BodyResourceDescriptionInterface {
name: string;
id: string;
img: string;
level?: number;
}
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
male1: { name: "male1", img: "resources/characters/pipoya/Male 01-1.png" },
male2: { name: "male2", img: "resources/characters/pipoya/Male 02-2.png" },
male3: { name: "male3", img: "resources/characters/pipoya/Male 03-4.png" },
male4: { name: "male4", img: "resources/characters/pipoya/Male 09-1.png" },
male5: { name: "male5", img: "resources/characters/pipoya/Male 10-3.png" },
male6: { name: "male6", img: "resources/characters/pipoya/Male 17-2.png" },
male7: { name: "male7", img: "resources/characters/pipoya/Male 18-1.png" },
male8: { name: "male8", img: "resources/characters/pipoya/Male 16-4.png" },
male9: { name: "male9", img: "resources/characters/pipoya/Male 07-2.png" },
male10: { name: "male10", img: "resources/characters/pipoya/Male 05-3.png" },
male11: { name: "male11", img: "resources/characters/pipoya/Teacher male 02.png" },
male12: { name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png" },
Female1: { name: "Female1", img: "resources/characters/pipoya/Female 01-1.png" },
Female2: { name: "Female2", img: "resources/characters/pipoya/Female 02-2.png" },
Female3: { name: "Female3", img: "resources/characters/pipoya/Female 03-4.png" },
Female4: { name: "Female4", img: "resources/characters/pipoya/Female 09-1.png" },
Female5: { name: "Female5", img: "resources/characters/pipoya/Female 10-3.png" },
Female6: { name: "Female6", img: "resources/characters/pipoya/Female 17-2.png" },
Female7: { name: "Female7", img: "resources/characters/pipoya/Female 18-1.png" },
Female8: { name: "Female8", img: "resources/characters/pipoya/Female 16-4.png" },
Female9: { name: "Female9", img: "resources/characters/pipoya/Female 07-2.png" },
Female10: { name: "Female10", img: "resources/characters/pipoya/Female 05-3.png" },
Female11: { name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png" },
Female12: { name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png" },
/**
* Temporary object to map layers to the old "level" concept.
*/
export const mapLayerToLevel = {
woka: -1,
body: 0,
eyes: 1,
hair: 2,
clothes: 3,
hat: 4,
accessory: 5,
};
export const COLOR_RESOURCES: BodyResourceDescriptionListInterface = {
color_1: { name: "color_1", img: "resources/customisation/character_color/character_color0.png" },
color_2: { name: "color_2", img: "resources/customisation/character_color/character_color1.png" },
color_3: { name: "color_3", img: "resources/customisation/character_color/character_color2.png" },
color_4: { name: "color_4", img: "resources/customisation/character_color/character_color3.png" },
color_5: { name: "color_5", img: "resources/customisation/character_color/character_color4.png" },
color_6: { name: "color_6", img: "resources/customisation/character_color/character_color5.png" },
color_7: { name: "color_7", img: "resources/customisation/character_color/character_color6.png" },
color_8: { name: "color_8", img: "resources/customisation/character_color/character_color7.png" },
color_9: { name: "color_9", img: "resources/customisation/character_color/character_color8.png" },
color_10: { name: "color_10", img: "resources/customisation/character_color/character_color9.png" },
color_11: { name: "color_11", img: "resources/customisation/character_color/character_color10.png" },
color_12: { name: "color_12", img: "resources/customisation/character_color/character_color11.png" },
color_13: { name: "color_13", img: "resources/customisation/character_color/character_color12.png" },
color_14: { name: "color_14", img: "resources/customisation/character_color/character_color13.png" },
color_15: { name: "color_15", img: "resources/customisation/character_color/character_color14.png" },
color_16: { name: "color_16", img: "resources/customisation/character_color/character_color15.png" },
color_17: { name: "color_17", img: "resources/customisation/character_color/character_color16.png" },
color_18: { name: "color_18", img: "resources/customisation/character_color/character_color17.png" },
color_19: { name: "color_19", img: "resources/customisation/character_color/character_color18.png" },
color_20: { name: "color_20", img: "resources/customisation/character_color/character_color19.png" },
color_21: { name: "color_21", img: "resources/customisation/character_color/character_color20.png" },
color_22: { name: "color_22", img: "resources/customisation/character_color/character_color21.png" },
color_23: { name: "color_23", img: "resources/customisation/character_color/character_color22.png" },
color_24: { name: "color_24", img: "resources/customisation/character_color/character_color23.png" },
color_25: { name: "color_25", img: "resources/customisation/character_color/character_color24.png" },
color_26: { name: "color_26", img: "resources/customisation/character_color/character_color25.png" },
color_27: { name: "color_27", img: "resources/customisation/character_color/character_color26.png" },
color_28: { name: "color_28", img: "resources/customisation/character_color/character_color27.png" },
color_29: { name: "color_29", img: "resources/customisation/character_color/character_color28.png" },
color_30: { name: "color_30", img: "resources/customisation/character_color/character_color29.png" },
color_31: { name: "color_31", img: "resources/customisation/character_color/character_color30.png" },
color_32: { name: "color_32", img: "resources/customisation/character_color/character_color31.png" },
color_33: { name: "color_33", img: "resources/customisation/character_color/character_color32.png" },
};
export enum PlayerTexturesKey {
Accessory = "accessory",
Body = "body",
Clothes = "clothes",
Eyes = "eyes",
Hair = "hair",
Hat = "hat",
Woka = "woka",
}
export const EYES_RESOURCES: BodyResourceDescriptionListInterface = {
eyes_1: { name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png" },
eyes_2: { name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png" },
eyes_3: { name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png" },
eyes_4: { name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png" },
eyes_5: { name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png" },
eyes_6: { name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png" },
eyes_7: { name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png" },
eyes_8: { name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png" },
eyes_9: { name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png" },
eyes_10: { name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png" },
eyes_11: { name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png" },
eyes_12: { name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png" },
eyes_13: { name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png" },
eyes_14: { name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png" },
eyes_15: { name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png" },
eyes_16: { name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png" },
eyes_17: { name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png" },
eyes_18: { name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png" },
eyes_19: { name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png" },
eyes_20: { name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png" },
eyes_21: { name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png" },
eyes_22: { name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png" },
eyes_23: { name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png" },
eyes_24: { name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png" },
eyes_25: { name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png" },
eyes_26: { name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png" },
eyes_27: { name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png" },
eyes_28: { name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png" },
eyes_29: { name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png" },
eyes_30: { name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png" },
};
export class PlayerTextures {
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
private EYES_RESOURCES: BodyResourceDescriptionListInterface = {};
private HAIR_RESOURCES: BodyResourceDescriptionListInterface = {};
private CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {};
private HATS_RESOURCES: BodyResourceDescriptionListInterface = {};
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
private LAYERS: BodyResourceDescriptionListInterface[] = [];
export const HAIR_RESOURCES: BodyResourceDescriptionListInterface = {
hair_1: { name: "hair_1", img: "resources/customisation/character_hairs/character_hairs0.png" },
hair_2: { name: "hair_2", img: "resources/customisation/character_hairs/character_hairs1.png" },
hair_3: { name: "hair_3", img: "resources/customisation/character_hairs/character_hairs2.png" },
hair_4: { name: "hair_4", img: "resources/customisation/character_hairs/character_hairs3.png" },
hair_5: { name: "hair_5", img: "resources/customisation/character_hairs/character_hairs4.png" },
hair_6: { name: "hair_6", img: "resources/customisation/character_hairs/character_hairs5.png" },
hair_7: { name: "hair_7", img: "resources/customisation/character_hairs/character_hairs6.png" },
hair_8: { name: "hair_8", img: "resources/customisation/character_hairs/character_hairs7.png" },
hair_9: { name: "hair_9", img: "resources/customisation/character_hairs/character_hairs8.png" },
hair_10: { name: "hair_10", img: "resources/customisation/character_hairs/character_hairs9.png" },
hair_11: { name: "hair_11", img: "resources/customisation/character_hairs/character_hairs10.png" },
hair_12: { name: "hair_12", img: "resources/customisation/character_hairs/character_hairs11.png" },
hair_13: { name: "hair_13", img: "resources/customisation/character_hairs/character_hairs12.png" },
hair_14: { name: "hair_14", img: "resources/customisation/character_hairs/character_hairs13.png" },
hair_15: { name: "hair_15", img: "resources/customisation/character_hairs/character_hairs14.png" },
hair_16: { name: "hair_16", img: "resources/customisation/character_hairs/character_hairs15.png" },
hair_17: { name: "hair_17", img: "resources/customisation/character_hairs/character_hairs16.png" },
hair_18: { name: "hair_18", img: "resources/customisation/character_hairs/character_hairs17.png" },
hair_19: { name: "hair_19", img: "resources/customisation/character_hairs/character_hairs18.png" },
hair_20: { name: "hair_20", img: "resources/customisation/character_hairs/character_hairs19.png" },
hair_21: { name: "hair_21", img: "resources/customisation/character_hairs/character_hairs20.png" },
hair_22: { name: "hair_22", img: "resources/customisation/character_hairs/character_hairs21.png" },
hair_23: { name: "hair_23", img: "resources/customisation/character_hairs/character_hairs22.png" },
hair_24: { name: "hair_24", img: "resources/customisation/character_hairs/character_hairs23.png" },
hair_25: { name: "hair_25", img: "resources/customisation/character_hairs/character_hairs24.png" },
hair_26: { name: "hair_26", img: "resources/customisation/character_hairs/character_hairs25.png" },
hair_27: { name: "hair_27", img: "resources/customisation/character_hairs/character_hairs26.png" },
hair_28: { name: "hair_28", img: "resources/customisation/character_hairs/character_hairs27.png" },
hair_29: { name: "hair_29", img: "resources/customisation/character_hairs/character_hairs28.png" },
hair_30: { name: "hair_30", img: "resources/customisation/character_hairs/character_hairs29.png" },
hair_31: { name: "hair_31", img: "resources/customisation/character_hairs/character_hairs30.png" },
hair_32: { name: "hair_32", img: "resources/customisation/character_hairs/character_hairs31.png" },
hair_33: { name: "hair_33", img: "resources/customisation/character_hairs/character_hairs32.png" },
hair_34: { name: "hair_34", img: "resources/customisation/character_hairs/character_hairs33.png" },
hair_35: { name: "hair_35", img: "resources/customisation/character_hairs/character_hairs34.png" },
hair_36: { name: "hair_36", img: "resources/customisation/character_hairs/character_hairs35.png" },
hair_37: { name: "hair_37", img: "resources/customisation/character_hairs/character_hairs36.png" },
hair_38: { name: "hair_38", img: "resources/customisation/character_hairs/character_hairs37.png" },
hair_39: { name: "hair_39", img: "resources/customisation/character_hairs/character_hairs38.png" },
hair_40: { name: "hair_40", img: "resources/customisation/character_hairs/character_hairs39.png" },
hair_41: { name: "hair_41", img: "resources/customisation/character_hairs/character_hairs40.png" },
hair_42: { name: "hair_42", img: "resources/customisation/character_hairs/character_hairs41.png" },
hair_43: { name: "hair_43", img: "resources/customisation/character_hairs/character_hairs42.png" },
hair_44: { name: "hair_44", img: "resources/customisation/character_hairs/character_hairs43.png" },
hair_45: { name: "hair_45", img: "resources/customisation/character_hairs/character_hairs44.png" },
hair_46: { name: "hair_46", img: "resources/customisation/character_hairs/character_hairs45.png" },
hair_47: { name: "hair_47", img: "resources/customisation/character_hairs/character_hairs46.png" },
hair_48: { name: "hair_48", img: "resources/customisation/character_hairs/character_hairs47.png" },
hair_49: { name: "hair_49", img: "resources/customisation/character_hairs/character_hairs48.png" },
hair_50: { name: "hair_50", img: "resources/customisation/character_hairs/character_hairs49.png" },
hair_51: { name: "hair_51", img: "resources/customisation/character_hairs/character_hairs50.png" },
hair_52: { name: "hair_52", img: "resources/customisation/character_hairs/character_hairs51.png" },
hair_53: { name: "hair_53", img: "resources/customisation/character_hairs/character_hairs52.png" },
hair_54: { name: "hair_54", img: "resources/customisation/character_hairs/character_hairs53.png" },
hair_55: { name: "hair_55", img: "resources/customisation/character_hairs/character_hairs54.png" },
hair_56: { name: "hair_56", img: "resources/customisation/character_hairs/character_hairs55.png" },
hair_57: { name: "hair_57", img: "resources/customisation/character_hairs/character_hairs56.png" },
hair_58: { name: "hair_58", img: "resources/customisation/character_hairs/character_hairs57.png" },
hair_59: { name: "hair_59", img: "resources/customisation/character_hairs/character_hairs58.png" },
hair_60: { name: "hair_60", img: "resources/customisation/character_hairs/character_hairs59.png" },
hair_61: { name: "hair_61", img: "resources/customisation/character_hairs/character_hairs60.png" },
hair_62: { name: "hair_62", img: "resources/customisation/character_hairs/character_hairs61.png" },
hair_63: { name: "hair_63", img: "resources/customisation/character_hairs/character_hairs62.png" },
hair_64: { name: "hair_64", img: "resources/customisation/character_hairs/character_hairs63.png" },
hair_65: { name: "hair_65", img: "resources/customisation/character_hairs/character_hairs64.png" },
hair_66: { name: "hair_66", img: "resources/customisation/character_hairs/character_hairs65.png" },
hair_67: { name: "hair_67", img: "resources/customisation/character_hairs/character_hairs66.png" },
hair_68: { name: "hair_68", img: "resources/customisation/character_hairs/character_hairs67.png" },
hair_69: { name: "hair_69", img: "resources/customisation/character_hairs/character_hairs68.png" },
hair_70: { name: "hair_70", img: "resources/customisation/character_hairs/character_hairs69.png" },
hair_71: { name: "hair_71", img: "resources/customisation/character_hairs/character_hairs70.png" },
hair_72: { name: "hair_72", img: "resources/customisation/character_hairs/character_hairs71.png" },
hair_73: { name: "hair_73", img: "resources/customisation/character_hairs/character_hairs72.png" },
hair_74: { name: "hair_74", img: "resources/customisation/character_hairs/character_hairs73.png" },
};
public loadPlayerTexturesMetadata(metadata: WokaList): void {
this.mapTexturesMetadataIntoResources(metadata);
}
export const CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {
clothes_1: { name: "clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png" },
clothes_2: { name: "clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png" },
clothes_3: { name: "clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png" },
clothes_4: { name: "clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png" },
clothes_5: { name: "clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png" },
clothes_6: { name: "clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png" },
clothes_7: { name: "clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png" },
clothes_8: { name: "clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png" },
clothes_9: { name: "clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png" },
clothes_10: { name: "clothes_10", img: "resources/customisation/character_clothes/character_clothes9.png" },
clothes_11: { name: "clothes_11", img: "resources/customisation/character_clothes/character_clothes10.png" },
clothes_12: { name: "clothes_12", img: "resources/customisation/character_clothes/character_clothes11.png" },
clothes_13: { name: "clothes_13", img: "resources/customisation/character_clothes/character_clothes12.png" },
clothes_14: { name: "clothes_14", img: "resources/customisation/character_clothes/character_clothes13.png" },
clothes_15: { name: "clothes_15", img: "resources/customisation/character_clothes/character_clothes14.png" },
clothes_16: { name: "clothes_16", img: "resources/customisation/character_clothes/character_clothes15.png" },
clothes_17: { name: "clothes_17", img: "resources/customisation/character_clothes/character_clothes16.png" },
clothes_18: { name: "clothes_18", img: "resources/customisation/character_clothes/character_clothes17.png" },
clothes_19: { name: "clothes_19", img: "resources/customisation/character_clothes/character_clothes18.png" },
clothes_20: { name: "clothes_20", img: "resources/customisation/character_clothes/character_clothes19.png" },
clothes_21: { name: "clothes_21", img: "resources/customisation/character_clothes/character_clothes20.png" },
clothes_22: { name: "clothes_22", img: "resources/customisation/character_clothes/character_clothes21.png" },
clothes_23: { name: "clothes_23", img: "resources/customisation/character_clothes/character_clothes22.png" },
clothes_24: { name: "clothes_24", img: "resources/customisation/character_clothes/character_clothes23.png" },
clothes_25: { name: "clothes_25", img: "resources/customisation/character_clothes/character_clothes24.png" },
clothes_26: { name: "clothes_26", img: "resources/customisation/character_clothes/character_clothes25.png" },
clothes_27: { name: "clothes_27", img: "resources/customisation/character_clothes/character_clothes26.png" },
clothes_28: { name: "clothes_28", img: "resources/customisation/character_clothes/character_clothes27.png" },
clothes_29: { name: "clothes_29", img: "resources/customisation/character_clothes/character_clothes28.png" },
clothes_30: { name: "clothes_30", img: "resources/customisation/character_clothes/character_clothes29.png" },
clothes_31: { name: "clothes_31", img: "resources/customisation/character_clothes/character_clothes30.png" },
clothes_32: { name: "clothes_32", img: "resources/customisation/character_clothes/character_clothes31.png" },
clothes_33: { name: "clothes_33", img: "resources/customisation/character_clothes/character_clothes32.png" },
clothes_34: { name: "clothes_34", img: "resources/customisation/character_clothes/character_clothes33.png" },
clothes_35: { name: "clothes_35", img: "resources/customisation/character_clothes/character_clothes34.png" },
clothes_36: { name: "clothes_36", img: "resources/customisation/character_clothes/character_clothes35.png" },
clothes_37: { name: "clothes_37", img: "resources/customisation/character_clothes/character_clothes36.png" },
clothes_38: { name: "clothes_38", img: "resources/customisation/character_clothes/character_clothes37.png" },
clothes_39: { name: "clothes_39", img: "resources/customisation/character_clothes/character_clothes38.png" },
clothes_40: { name: "clothes_40", img: "resources/customisation/character_clothes/character_clothes39.png" },
clothes_41: { name: "clothes_41", img: "resources/customisation/character_clothes/character_clothes40.png" },
clothes_42: { name: "clothes_42", img: "resources/customisation/character_clothes/character_clothes41.png" },
clothes_43: { name: "clothes_43", img: "resources/customisation/character_clothes/character_clothes42.png" },
clothes_44: { name: "clothes_44", img: "resources/customisation/character_clothes/character_clothes43.png" },
clothes_45: { name: "clothes_45", img: "resources/customisation/character_clothes/character_clothes44.png" },
clothes_46: { name: "clothes_46", img: "resources/customisation/character_clothes/character_clothes45.png" },
clothes_47: { name: "clothes_47", img: "resources/customisation/character_clothes/character_clothes46.png" },
clothes_48: { name: "clothes_48", img: "resources/customisation/character_clothes/character_clothes47.png" },
clothes_49: { name: "clothes_49", img: "resources/customisation/character_clothes/character_clothes48.png" },
clothes_50: { name: "clothes_50", img: "resources/customisation/character_clothes/character_clothes49.png" },
clothes_51: { name: "clothes_51", img: "resources/customisation/character_clothes/character_clothes50.png" },
clothes_52: { name: "clothes_52", img: "resources/customisation/character_clothes/character_clothes51.png" },
clothes_53: { name: "clothes_53", img: "resources/customisation/character_clothes/character_clothes52.png" },
clothes_54: { name: "clothes_54", img: "resources/customisation/character_clothes/character_clothes53.png" },
clothes_55: { name: "clothes_55", img: "resources/customisation/character_clothes/character_clothes54.png" },
clothes_56: { name: "clothes_56", img: "resources/customisation/character_clothes/character_clothes55.png" },
clothes_57: { name: "clothes_57", img: "resources/customisation/character_clothes/character_clothes56.png" },
clothes_58: { name: "clothes_58", img: "resources/customisation/character_clothes/character_clothes57.png" },
clothes_59: { name: "clothes_59", img: "resources/customisation/character_clothes/character_clothes58.png" },
clothes_60: { name: "clothes_60", img: "resources/customisation/character_clothes/character_clothes59.png" },
clothes_61: { name: "clothes_61", img: "resources/customisation/character_clothes/character_clothes60.png" },
clothes_62: { name: "clothes_62", img: "resources/customisation/character_clothes/character_clothes61.png" },
clothes_63: { name: "clothes_63", img: "resources/customisation/character_clothes/character_clothes62.png" },
clothes_64: { name: "clothes_64", img: "resources/customisation/character_clothes/character_clothes63.png" },
clothes_65: { name: "clothes_65", img: "resources/customisation/character_clothes/character_clothes64.png" },
clothes_66: { name: "clothes_66", img: "resources/customisation/character_clothes/character_clothes65.png" },
clothes_67: { name: "clothes_67", img: "resources/customisation/character_clothes/character_clothes66.png" },
clothes_68: { name: "clothes_68", img: "resources/customisation/character_clothes/character_clothes67.png" },
clothes_69: { name: "clothes_69", img: "resources/customisation/character_clothes/character_clothes68.png" },
clothes_70: { name: "clothes_70", img: "resources/customisation/character_clothes/character_clothes69.png" },
clothes_pride_shirt: {
name: "clothes_pride_shirt",
img: "resources/customisation/character_clothes/pride_shirt.png",
},
clothes_black_hoodie: {
name: "clothes_black_hoodie",
img: "resources/customisation/character_clothes/black_hoodie.png",
},
clothes_white_hoodie: {
name: "clothes_white_hoodie",
img: "resources/customisation/character_clothes/white_hoodie.png",
},
clothes_engelbert: { name: "clothes_engelbert", img: "resources/customisation/character_clothes/engelbert.png" },
};
public getTexturesResources(key: PlayerTexturesKey): BodyResourceDescriptionListInterface {
switch (key) {
case PlayerTexturesKey.Accessory:
return this.ACCESSORIES_RESOURCES;
case PlayerTexturesKey.Body:
return this.COLOR_RESOURCES;
case PlayerTexturesKey.Clothes:
return this.CLOTHES_RESOURCES;
case PlayerTexturesKey.Eyes:
return this.EYES_RESOURCES;
case PlayerTexturesKey.Hair:
return this.HAIR_RESOURCES;
case PlayerTexturesKey.Hat:
return this.HATS_RESOURCES;
case PlayerTexturesKey.Woka:
return this.PLAYER_RESOURCES;
}
}
export const HATS_RESOURCES: BodyResourceDescriptionListInterface = {
hats_1: { name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png" },
hats_2: { name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png" },
hats_3: { name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png" },
hats_4: { name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png" },
hats_5: { name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png" },
hats_6: { name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png" },
hats_7: { name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png" },
hats_8: { name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png" },
hats_9: { name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png" },
hats_10: { name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png" },
hats_11: { name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png" },
hats_12: { name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png" },
hats_13: { name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png" },
hats_14: { name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png" },
hats_15: { name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png" },
hats_16: { name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png" },
hats_17: { name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png" },
hats_18: { name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png" },
hats_19: { name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png" },
hats_20: { name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png" },
hats_21: { name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png" },
hats_22: { name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png" },
hats_23: { name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png" },
hats_24: { name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png" },
hats_25: { name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png" },
hats_26: { name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png" },
tinfoil_hat1: { name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png" },
};
public getLayers(): BodyResourceDescriptionListInterface[] {
return this.LAYERS;
}
export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {
accessory_1: {
name: "accessory_1",
img: "resources/customisation/character_accessories/character_accessories1.png",
},
accessory_2: {
name: "accessory_2",
img: "resources/customisation/character_accessories/character_accessories2.png",
},
accessory_3: {
name: "accessory_3",
img: "resources/customisation/character_accessories/character_accessories3.png",
},
accessory_4: {
name: "accessory_4",
img: "resources/customisation/character_accessories/character_accessories4.png",
},
accessory_5: {
name: "accessory_5",
img: "resources/customisation/character_accessories/character_accessories5.png",
},
accessory_6: {
name: "accessory_6",
img: "resources/customisation/character_accessories/character_accessories6.png",
},
accessory_7: {
name: "accessory_7",
img: "resources/customisation/character_accessories/character_accessories7.png",
},
accessory_8: {
name: "accessory_8",
img: "resources/customisation/character_accessories/character_accessories8.png",
},
accessory_9: {
name: "accessory_9",
img: "resources/customisation/character_accessories/character_accessories9.png",
},
accessory_10: {
name: "accessory_10",
img: "resources/customisation/character_accessories/character_accessories10.png",
},
accessory_11: {
name: "accessory_11",
img: "resources/customisation/character_accessories/character_accessories11.png",
},
accessory_12: {
name: "accessory_12",
img: "resources/customisation/character_accessories/character_accessories12.png",
},
accessory_13: {
name: "accessory_13",
img: "resources/customisation/character_accessories/character_accessories13.png",
},
accessory_14: {
name: "accessory_14",
img: "resources/customisation/character_accessories/character_accessories14.png",
},
accessory_15: {
name: "accessory_15",
img: "resources/customisation/character_accessories/character_accessories15.png",
},
accessory_16: {
name: "accessory_16",
img: "resources/customisation/character_accessories/character_accessories16.png",
},
accessory_17: {
name: "accessory_17",
img: "resources/customisation/character_accessories/character_accessories17.png",
},
accessory_18: {
name: "accessory_18",
img: "resources/customisation/character_accessories/character_accessories18.png",
},
accessory_19: {
name: "accessory_19",
img: "resources/customisation/character_accessories/character_accessories19.png",
},
accessory_20: {
name: "accessory_20",
img: "resources/customisation/character_accessories/character_accessories20.png",
},
accessory_21: {
name: "accessory_21",
img: "resources/customisation/character_accessories/character_accessories21.png",
},
accessory_22: {
name: "accessory_22",
img: "resources/customisation/character_accessories/character_accessories22.png",
},
accessory_23: {
name: "accessory_23",
img: "resources/customisation/character_accessories/character_accessories23.png",
},
accessory_24: {
name: "accessory_24",
img: "resources/customisation/character_accessories/character_accessories24.png",
},
accessory_25: {
name: "accessory_25",
img: "resources/customisation/character_accessories/character_accessories25.png",
},
accessory_26: {
name: "accessory_26",
img: "resources/customisation/character_accessories/character_accessories26.png",
},
accessory_27: {
name: "accessory_27",
img: "resources/customisation/character_accessories/character_accessories27.png",
},
accessory_28: {
name: "accessory_28",
img: "resources/customisation/character_accessories/character_accessories28.png",
},
accessory_29: {
name: "accessory_29",
img: "resources/customisation/character_accessories/character_accessories29.png",
},
accessory_30: {
name: "accessory_30",
img: "resources/customisation/character_accessories/character_accessories30.png",
},
accessory_31: {
name: "accessory_31",
img: "resources/customisation/character_accessories/character_accessories31.png",
},
accessory_32: {
name: "accessory_32",
img: "resources/customisation/character_accessories/character_accessories32.png",
},
accessory_mate_bottle: {
name: "accessory_mate_bottle",
img: "resources/customisation/character_accessories/mate_bottle1.png",
},
accessory_mask: { name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png" },
};
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);
this.HAIR_RESOURCES = this.getMappedResources(metadata.hair);
this.CLOTHES_RESOURCES = this.getMappedResources(metadata.clothes);
this.HATS_RESOURCES = this.getMappedResources(metadata.hat);
this.ACCESSORIES_RESOURCES = this.getMappedResources(metadata.accessory);
export const LAYERS: BodyResourceDescriptionListInterface[] = [
COLOR_RESOURCES,
EYES_RESOURCES,
HAIR_RESOURCES,
CLOTHES_RESOURCES,
HATS_RESOURCES,
ACCESSORIES_RESOURCES,
];
this.LAYERS = [
this.COLOR_RESOURCES,
this.EYES_RESOURCES,
this.HAIR_RESOURCES,
this.CLOTHES_RESOURCES,
this.HATS_RESOURCES,
this.ACCESSORIES_RESOURCES,
];
}
private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
const resources: BodyResourceDescriptionListInterface = {};
if (!category) {
return {};
}
for (const collection of category.collections) {
for (const texture of collection.textures) {
resources[texture.id] = { id: texture.id, img: texture.url };
}
}
return resources;
}
}
export const OBJECTS: BodyResourceDescriptionInterface[] = [
{ name: "teleportation", img: "resources/objects/teleportation.png" },
{ id: "teleportation", img: "resources/objects/teleportation.png" },
];
@@ -1,129 +1,69 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type { CharacterTexture } from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
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;
frameHeight: number;
}
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
export const loadAllLayers = (
load: LoaderPlugin,
playerTextures: PlayerTextures
): BodyResourceDescriptionInterface[][] => {
const returnArray: BodyResourceDescriptionInterface[][] = [];
LAYERS.forEach((layer) => {
playerTextures.getLayers().forEach((layer) => {
const layerArray: BodyResourceDescriptionInterface[] = [];
Object.values(layer).forEach((textureDescriptor) => {
layerArray.push(textureDescriptor);
load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
load.spritesheet(textureDescriptor.id, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
});
returnArray.push(layerArray);
});
return returnArray;
};
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(PLAYER_RESOURCES);
export const loadAllDefaultModels = (
load: LoaderPlugin,
playerTextures: PlayerTextures
): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(playerTextures.getTexturesResources(PlayerTexturesKey.Woka));
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 });
load.spritesheet(playerResource.id, playerResource.img, { frameWidth: 32, frameHeight: 32 });
});
return returnArray;
};
export const loadCustomTexture = (
loaderPlugin: LoaderPlugin,
texture: CharacterTexture
): CancelablePromise<BodyResourceDescriptionInterface> => {
const name = "customCharacterTexture" + texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
export const loadWokaTexture = (
superLoaderPlugin: SuperLoaderPlugin,
texture: BodyResourceDescriptionInterface
): CancelablePromise<Texture> => {
return superLoaderPlugin.spritesheet(texture.id, texture.img, {
frameWidth: 32,
frameHeight: 32,
});
};
export const lazyLoadPlayerCharacterTextures = (
loadPlugin: LoaderPlugin,
texturekeys: Array<string | BodyResourceDescriptionInterface>
superLoaderPlugin: SuperLoaderPlugin,
textures: BodyResourceDescriptionInterface[]
): CancelablePromise<string[]> => {
const promisesList: CancelablePromise<unknown>[] = [];
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
try {
//TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
promisesList.push(
createLoadingPromise(loadPlugin, playerResourceDescriptor, {
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(() => texturekeys);
} else {
returnPromise = CancelablePromise.resolve(texturekeys);
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.name : key;
return returnPromise.then(() =>
textures.map((key) => {
return key.id;
})
);
};
export const getRessourceDescriptor = (
textureKey: string | BodyResourceDescriptionInterface
): BodyResourceDescriptionInterface => {
if (typeof textureKey !== "string" && textureKey.img) {
return textureKey;
}
const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name;
const playerResource = PLAYER_RESOURCES[textureName];
if (playerResource !== undefined) return playerResource;
for (let i = 0; i < LAYERS.length; i++) {
const playerResource = LAYERS[i][textureName];
if (playerResource !== undefined) return playerResource;
}
throw new Error("Could not find a data for texture " + textureName);
};
export const createLoadingPromise = (
loadPlugin: LoaderPlugin,
playerResourceDescriptor: BodyResourceDescriptionInterface,
frameConfig: FrameConfig
) => {
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor);
}
cancel(() => {
loadPlugin.off("loaderror");
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name);
return;
});
loadPlugin.spritesheet(playerResourceDescriptor.name, 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.name, successCallback);
loadPlugin.off("loaderror", errorCallback);
};
const successCallback = () => {
loadPlugin.off("loaderror", errorCallback);
res(playerResourceDescriptor);
};
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
loadPlugin.on("loaderror", errorCallback);
});
};
+45 -22
View File
@@ -1,5 +1,5 @@
import { requestVisitCardsStore } from "../../Stores/GameStore";
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { ActionsMenuAction, ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { Character } from "../Entity/Character";
import type { GameScene } from "../Game/GameScene";
import type { PointInterface } from "../../Connexion/ConnexionModels";
@@ -8,21 +8,28 @@ import type { Unsubscriber } from "svelte/store";
import type { ActivatableInterface } from "../Game/ActivatableInterface";
import type CancelablePromise from "cancelable-promise";
import LL from "../../i18n/i18n-svelte";
import { blackListManager } from "../../WebRtc/BlackListManager";
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
export enum RemotePlayerEvent {
Clicked = "Clicked",
}
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
*/
export class RemotePlayer extends Character implements ActivatableInterface {
public userId: number;
public readonly userId: number;
public readonly userUuid: string;
public readonly activationRadius: number;
private registeredActions: { actionName: string; callback: Function }[];
private visitCardUrl: string | null;
private isActionsMenuInitialized: boolean = false;
private actionsMenuStoreUnsubscriber: Unsubscriber;
constructor(
userId: number,
userUuid: string,
Scene: GameScene,
x: number,
y: number,
@@ -39,10 +46,9 @@ export class RemotePlayer extends Character implements ActivatableInterface {
//set data
this.userId = userId;
this.userUuid = userUuid;
this.visitCardUrl = visitCardUrl;
this.registeredActions = [];
this.registerDefaultActionsMenuActions();
this.setClickable(this.registeredActions.length > 0);
this.setClickable(this.getDefaultActionsMenuActions().length > 0);
this.activationRadius = activationRadius ?? 96;
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
this.isActionsMenuInitialized = value ? true : false;
@@ -63,17 +69,19 @@ export class RemotePlayer extends Character implements ActivatableInterface {
}
}
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void {
this.registeredActions.push(action);
this.updateIsClickable();
public registerActionsMenuAction(action: ActionsMenuAction): void {
actionsMenuStore.addAction({
...action,
priority: action.priority ?? 0,
callback: () => {
action.callback();
actionsMenuStore.clear();
},
});
}
public unregisterActionsMenuAction(actionName: string) {
const index = this.registeredActions.findIndex((action) => action.actionName === actionName);
if (index !== -1) {
this.registeredActions.splice(index, 1);
}
this.updateIsClickable();
actionsMenuStore.removeAction(actionName);
}
public activate(): void {
@@ -90,37 +98,52 @@ export class RemotePlayer extends Character implements ActivatableInterface {
return this.isClickable();
}
private updateIsClickable(): void {
this.setClickable(this.registeredActions.length > 0);
}
private toggleActionsMenu(): void {
if (this.isActionsMenuInitialized) {
actionsMenuStore.clear();
return;
}
actionsMenuStore.initialize(this.playerName);
for (const action of this.registeredActions) {
actionsMenuStore.addAction(action.actionName, action.callback);
for (const action of this.getDefaultActionsMenuActions()) {
actionsMenuStore.addAction(action);
}
}
private registerDefaultActionsMenuActions(): void {
private getDefaultActionsMenuActions(): ActionsMenuAction[] {
const actions: ActionsMenuAction[] = [];
if (this.visitCardUrl) {
this.registeredActions.push({
actions.push({
actionName: LL.woka.menu.businessCard(),
protected: true,
priority: 1,
callback: () => {
requestVisitCardsStore.set(this.visitCardUrl);
actionsMenuStore.clear();
},
});
}
actions.push({
actionName: blackListManager.isBlackListed(this.userUuid)
? LL.report.block.unblock()
: LL.report.block.block(),
protected: true,
priority: -1,
style: "is-error",
callback: () => {
showReportScreenStore.set({ userId: this.userId, userName: this.name });
actionsMenuStore.clear();
},
});
return actions;
}
private bindEventHandlers(): void {
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) {
this.toggleActionsMenu();
this.emit(RemotePlayerEvent.Clicked);
}
});
}
+10 -2
View File
@@ -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
+5 -18
View File
@@ -126,19 +126,7 @@ export class GameMap {
for (let y = 0; y < this.map.height; y += 1) {
const row: number[] = [];
for (let x = 0; x < this.map.width; x += 1) {
row.push(this.isCollidingAt(x, y) ? 1 : 0);
}
grid.push(row);
}
return grid;
}
public getWalkingCostGrid(): number[][] {
const grid: number[][] = [];
for (let y = 0; y < this.map.height; y += 1) {
const row: number[] = [];
for (let x = 0; x < this.map.width; x += 1) {
row.push(this.getWalkingCostAt(x, y));
row.push(this.isCollidingAt(x, y) ? 1 : this.isExitTile(x, y) ? 2 : 0);
}
grid.push(row);
}
@@ -355,8 +343,7 @@ export class GameMap {
return false;
}
private getWalkingCostAt(x: number, y: number): number {
const bigCost = 100;
private isExitTile(x: number, y: number): boolean {
for (const layer of this.phaserLayers) {
if (!layer.visible) {
continue;
@@ -369,16 +356,16 @@ export class GameMap {
tile &&
(tile.properties[GameMapProperties.EXIT_URL] || tile.properties[GameMapProperties.EXIT_SCENE_URL])
) {
return bigCost;
return true;
}
for (const property of layer.layer.properties) {
//@ts-ignore
if (property.name && property.name === "exitUrl") {
return bigCost;
return true;
}
}
}
return 0;
return false;
}
private triggerAllProperties(): void {
+81 -40
View File
@@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager";
import { SharedVariablesManager } from "./SharedVariablesManager";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } 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";
@@ -30,7 +30,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import { SimplePeer } from "../../WebRtc/SimplePeer";
import { Loader } from "../Components/Loader";
import { RemotePlayer } from "../Entity/RemotePlayer";
import { RemotePlayer, RemotePlayerEvent } from "../Entity/RemotePlayer";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
import { PlayerAnimationDirections } from "../Player/Animation";
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
@@ -98,6 +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 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;
@@ -163,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;
@@ -221,6 +221,7 @@ export class GameScene extends DirtyScene {
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({
@@ -233,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
@@ -247,13 +245,6 @@ export class GameScene extends DirtyScene {
//initialize frame event of scripting API
this.listenToIframeEvents();
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture).catch((e) => console.error(e));
}
}
this.load.image("iconTalk", "/resources/icons/icon_talking.png");
if (touchScreenManager.supportTouchScreen) {
@@ -416,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?)
@@ -574,7 +565,6 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionGrid(),
this.gameMap.getWalkingCostGrid(),
this.gameMap.getTileDimensions()
);
@@ -618,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
@@ -644,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();
@@ -661,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) {
@@ -682,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) => {
@@ -715,7 +715,11 @@ export class GameScene extends DirtyScene {
this.currentPlayerGroupId = groupId;
});
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises])
Promise.all([
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
...scriptPromises,
this.CurrentPlayer.getTextureLoadedPromise() as Promise<unknown>,
])
.then(() => {
this.scene.wake();
})
@@ -752,6 +756,14 @@ export class GameScene extends DirtyScene {
.then((onConnect: OnConnectInterface) => {
this.connection = onConnect.connection;
lazyLoadPlayerCharacterTextures(this.superLoad, onConnect.room.characterLayers)
.then((layers) => {
this.currentPlayerTexturesResolve(layers);
})
.catch((e) => {
this.currentPlayerTexturesReject(e);
});
playersStore.connectToRoomConnection(this.connection);
userIsAdminStore.set(this.connection.hasTag("admin"));
@@ -878,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()) {
@@ -1122,6 +1134,23 @@ ${escapedMessage}
})
);
this.iframeSubscriptionList.push(
iframeListener.addActionsMenuKeyToRemotePlayerStream.subscribe((data) => {
this.MapPlayersByKey.get(data.id)?.registerActionsMenuAction({
actionName: data.actionKey,
callback: () => {
iframeListener.sendActionsMenuActionClickedEvent({ actionName: data.actionKey, id: data.id });
},
});
})
);
this.iframeSubscriptionList.push(
iframeListener.removeActionsMenuKeyFromRemotePlayerEvent.subscribe((data) => {
this.MapPlayersByKey.get(data.id)?.unregisterActionsMenuAction(data.actionKey);
})
);
this.iframeSubscriptionList.push(
iframeListener.trackCameraUpdateStream.subscribe(() => {
if (!this.firstCameraUpdateSent) {
@@ -1280,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,
@@ -1303,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;
@@ -1466,7 +1495,7 @@ ${escapedMessage}
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
}
}
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid(), this.gameMap.getWalkingCostGrid());
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
this.markDirty();
}
@@ -1548,7 +1577,7 @@ ${escapedMessage}
this.messageSubscription?.unsubscribe();
this.userInputManager.destroy();
this.pinchManager?.destroy();
this.emoteManager.destroy();
this.emoteManager?.destroy();
this.cameraManager.destroy();
this.peerStoreUnsubscribe();
this.emoteUnsubscribe();
@@ -1614,6 +1643,8 @@ ${escapedMessage}
}
})
.catch((reason) => console.warn(reason));
urlManager.clearHashParameter();
} catch (err) {
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
}
@@ -1705,16 +1736,23 @@ ${escapedMessage}
}
}
// The promise that will resolve to the current player texture. This will be available only after connection is established.
private currentPlayerTexturesResolve!: (value: string[]) => void;
private currentPlayerTexturesReject!: (reason: unknown) => void;
private currentPlayerTexturesPromise: CancelablePromise<string[]> = new CancelablePromise((resolve, reject) => {
this.currentPlayerTexturesResolve = resolve;
this.currentPlayerTexturesReject = reject;
});
private createCurrentPlayer() {
//TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try {
this.CurrentPlayer = new Player(
this,
this.startPositionCalculator.startPosition.x,
this.startPositionCalculator.startPosition.y,
this.playerName,
texturesPromise,
this.currentPlayerTexturesPromise,
PlayerAnimationDirections.Down,
false,
this.companion,
@@ -1898,9 +1936,10 @@ ${escapedMessage}
return;
}
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
const texturesPromise = lazyLoadPlayerCharacterTextures(this.superLoad, addPlayerData.characterLayers);
const player = new RemotePlayer(
addPlayerData.userId,
addPlayerData.userUuid,
this,
addPlayerData.position.x,
addPlayerData.position.y,
@@ -1928,6 +1967,10 @@ ${escapedMessage}
this.activatablesManager.handlePointerOutActivatableObject();
this.markDirty();
});
player.on(RemotePlayerEvent.Clicked, () => {
iframeListener.sendRemotePlayerClickedEvent({ id: player.userId });
});
}
/**
@@ -2070,8 +2113,6 @@ ${escapedMessage}
right: camera.scrollX + camera.width,
bottom: camera.scrollY + camera.height,
});
this.loader.resize();
}
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
@@ -1,41 +1,18 @@
import { ResizableScene } from "./ResizableScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import type { CharacterTexture } from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, PlayerTexturesKey } from "../Entity/PlayerTextures";
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 {
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level === -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises);
}
protected playerTextures: PlayerTextures;
protected superLoad: SuperLoaderPlugin;
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures();
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of textures) {
if (texture.level !== -1) {
continue;
}
promises.push(loadCustomTexture(this.load, texture));
}
}
return Promise.all(promises);
}
private getTextures(): CharacterTexture[] | undefined {
const localUser = localUserStore.getLocalUser();
return localUser?.textures;
constructor(params: { key: string }) {
super(params);
this.playerTextures = new PlayerTextures();
this.superLoad = new SuperLoaderPlugin(this);
}
}
+28 -23
View File
@@ -15,6 +15,8 @@ import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
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";
@@ -40,25 +42,28 @@ export class CustomizeScene extends AbstractCharacterScene {
}
preload() {
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;
})
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey);
this.superLoad
.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
},
withCredentials: true,
},
(key, type, data) => {
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.layers = loadAllLayers(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
this.layers = loadAllLayers(this.load);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
this.loader.addLoader();
}
@@ -192,13 +197,13 @@ export class CustomizeScene extends AbstractCharacterScene {
const children: Array<string> = new Array<string>();
for (let j = 0; j <= layerNumber; j++) {
if (j === layerNumber) {
children.push(this.layers[j][selectedItem].name);
children.push(this.layers[j][selectedItem].id);
} else {
const layer = this.selectedLayers[j];
if (layer === undefined) {
continue;
}
children.push(this.layers[j][layer].name);
children.push(this.layers[j][layer].id);
}
}
return children;
@@ -276,7 +281,7 @@ export class CustomizeScene extends AbstractCharacterScene {
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
layers.push(this.layers[i][layerItem].id);
}
i++;
}
@@ -287,14 +292,14 @@ export class CustomizeScene extends AbstractCharacterScene {
analyticsClient.validationWoka("CustomizeWoka");
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
this.scene.stop(CustomizeSceneName);
waScaleManager.restoreZoom();
gameManager.tryResumingGame(EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
}
public backToPreviousScene() {
this.scene.sleep(CustomizeSceneName);
this.scene.stop(CustomizeSceneName);
waScaleManager.restoreZoom();
this.scene.run(SelectCharacterSceneName);
customCharacterSceneVisibleStore.set(false);
+17
View File
@@ -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 {}
}
+8
View File
@@ -7,6 +7,8 @@ import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
import LL from "../../i18n/i18n-svelte";
import { get } from "svelte/store";
import { localeDetector } from "../../i18n/locales";
import { PlayerTextures } from "../Entity/PlayerTextures";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
export const EntrySceneName = "EntryScene";
@@ -15,6 +17,8 @@ export const EntrySceneName = "EntryScene";
* and to route to the next correct scene.
*/
export class EntryScene extends Scene {
private localeLoaded: boolean = false;
constructor() {
super({
key: EntrySceneName,
@@ -30,6 +34,10 @@ export class EntryScene extends Scene {
}
create() {
this.loadLocale();
}
private loadLocale(): void {
localeDetector()
.then(() => {
gameManager
+4 -1
View File
@@ -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);
+43 -17
View File
@@ -5,7 +5,7 @@ import { CustomizeSceneName } from "./CustomizeScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
import { Loader } from "../Components/Loader";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { BodyResourceDescriptionInterface, PlayerTextures } from "../Entity/PlayerTextures";
import { AbstractCharacterScene } from "./AbstractCharacterScene";
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
import { touchScreenManager } from "../../Touch/TouchScreenManager";
@@ -14,6 +14,9 @@ import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterSt
import { waScaleManager } from "../Services/WaScaleManager";
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";
@@ -38,25 +41,39 @@ export class SelectCharacterScene extends AbstractCharacterScene {
key: SelectCharacterSceneName,
});
this.loader = new Loader(this);
this.playerTextures = new PlayerTextures();
}
preload() {
this.loadSelectSceneCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey);
this.superLoad
.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
},
withCredentials: true,
},
(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.playerModels = loadAllDefaultModels(this.load);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
this.loader.addLoader();
}
create() {
customizeAvailableStore.set(this.isCustomizationAvailable());
selectCharacterSceneVisibleStore.set(true);
this.events.addListener("wake", () => {
waScaleManager.saveZoom();
@@ -130,16 +147,16 @@ export class SelectCharacterScene extends AbstractCharacterScene {
const playerResource = this.playerModels[i];
//check already exist texture
if (this.players.find((c) => c.texture.key === playerResource.name)) {
if (this.players.find((c) => c.texture.key === playerResource.id)) {
continue;
}
const [middleX, middleY] = this.getCharacterPosition();
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
const player = this.physics.add.sprite(middleX, middleY, playerResource.id, 0);
this.setUpPlayer(player, i);
this.anims.create({
key: playerResource.name,
frames: this.anims.generateFrameNumbers(playerResource.name, { start: 0, end: 11 }),
key: playerResource.id,
frames: this.anims.generateFrameNumbers(playerResource.id, { start: 0, end: 11 }),
frameRate: 8,
repeat: -1,
});
@@ -164,7 +181,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.currentSelectUser = 0;
}
this.selectedPlayer = this.players[this.currentSelectUser];
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].id);
}
protected moveUser() {
@@ -247,9 +264,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
}
protected updateSelectedPlayer(): void {
this.selectedPlayer?.anims.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
this.selectedPlayer?.anims?.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
const player = this.players[this.currentSelectUser];
player.play(this.playerModels[this.currentSelectUser].name);
player?.play(this.playerModels[this.currentSelectUser].id);
this.selectedPlayer = player;
localUserStore.setPlayerCharacterIndex(this.currentSelectUser);
}
@@ -274,4 +291,13 @@ export class SelectCharacterScene extends AbstractCharacterScene {
//move position of user
this.moveUser();
}
private isCustomizationAvailable(): boolean {
for (const layer of this.playerTextures.getLayers()) {
if (Object.keys(layer).length > 0) {
return true;
}
}
return false;
}
}
+1 -1
View File
@@ -76,7 +76,7 @@ export class Player extends Character {
speed?: number
): Promise<{ x: number; y: number; cancelled: boolean }> {
const isPreviousPathInProgress = this.pathToFollow !== undefined && this.pathToFollow.length > 0;
// take collider offset into consideraton
// take collider offset into consideration
this.pathToFollow = this.adjustPathToFollowToColliderBounds(path);
this.pathWalkingSpeed = speed;
return new Promise((resolve) => {
@@ -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();
}
});
}
}
+12 -8
View File
@@ -1,8 +1,15 @@
import { writable } from "svelte/store";
export type ActionsMenuAction = {
actionName: string;
callback: Function;
protected?: boolean;
priority?: number;
style?: "is-success" | "is-error" | "is-primary";
};
export interface ActionsMenuData {
playerName: string;
actions: { actionName: string; callback: Function }[];
actions: Map<string, ActionsMenuAction>;
}
function createActionsMenuStore() {
@@ -13,21 +20,18 @@ function createActionsMenuStore() {
initialize: (playerName: string) => {
set({
playerName,
actions: [],
actions: new Map<string, ActionsMenuAction>(),
});
},
addAction: (actionName: string, callback: Function) => {
addAction: (action: ActionsMenuAction) => {
update((data) => {
data?.actions.push({ actionName, callback });
data?.actions.set(action.actionName, action);
return data;
});
},
removeAction: (actionName: string) => {
update((data) => {
const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName);
if (actionIndex !== undefined && actionIndex != -1) {
data?.actions.splice(actionIndex, 1);
}
data?.actions.delete(actionName);
return data;
});
},
+19 -3
View File
@@ -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();
}
};
});
@@ -0,0 +1,3 @@
import { writable } from "svelte/store";
export const customizeAvailableStore = writable(false);
+4
View File
@@ -58,6 +58,10 @@ class UrlManager {
return this.getHashParameters()[name];
}
public clearHashParameter(): void {
window.location.hash = "";
}
private getHashParameters(): Record<string, string> {
return window.location.hash
.substring(1)
+4 -18
View File
@@ -8,27 +8,21 @@ export class PathfindingManager {
private grid: number[][];
private tileDimensions: { width: number; height: number };
constructor(
scene: Phaser.Scene,
collisionsGrid: number[][],
walkingCostGrid: number[][],
tileDimensions: { width: number; height: number }
) {
constructor(scene: Phaser.Scene, collisionsGrid: number[][], tileDimensions: { width: number; height: number }) {
this.scene = scene;
this.easyStar = new EasyStar.js();
this.easyStar.enableDiagonals();
this.easyStar.disableCornerCutting();
this.easyStar.setTileCost(2, 100);
this.grid = collisionsGrid;
this.tileDimensions = tileDimensions;
this.setEasyStarGrid(collisionsGrid);
this.setWalkingCostGrid(walkingCostGrid);
}
public setCollisionGrid(collisionGrid: number[][], walkingCostGrid: number[][]): void {
public setCollisionGrid(collisionGrid: number[][]): void {
this.setEasyStarGrid(collisionGrid);
this.setWalkingCostGrid(walkingCostGrid);
}
public async findPath(
@@ -120,15 +114,7 @@ export class PathfindingManager {
private setEasyStarGrid(grid: number[][]): void {
this.easyStar.setGrid(grid);
this.easyStar.setAcceptableTiles([0]); // zeroes are walkable
}
private setWalkingCostGrid(grid: number[][]): void {
for (let y = 0; y < grid.length; y += 1) {
for (let x = 0; x < grid[y].length; x += 1) {
this.easyStar.setAdditionalPointCost(x, y, grid[y][x]);
}
}
this.easyStar.setAcceptableTiles([0, 2]); // zeroes are walkable, 2 are exits, also walkable
}
private logGridToTheConsole(grid: number[][]): void {
+4 -1
View File
@@ -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,
+4 -1
View File
@@ -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);
+9 -1
View File
@@ -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 -1
View File
@@ -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",
};
+4
View File
@@ -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",
+7
View File
@@ -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 -1
View File
@@ -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",
};
+4
View File
@@ -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",
+7
View File
@@ -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 -1
View File
@@ -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",
};
+4
View File
@@ -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",
+7
View File
@@ -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",
+2
View File
@@ -16,6 +16,7 @@ import { coWebsiteManager } from "./WebRtc/CoWebsiteManager";
import { localUserStore } from "./Connexion/LocalUserStore";
import { ErrorScene } from "./Phaser/Reconnecting/ErrorScene";
import { iframeListener } from "./Api/IframeListener";
import { desktopApi } from "./Api/desktop/index";
import { SelectCharacterMobileScene } from "./Phaser/Login/SelectCharacterMobileScene";
import { HdpiManager } from "./Phaser/Services/HdpiManager";
import { waScaleManager } from "./Phaser/Services/WaScaleManager";
@@ -154,6 +155,7 @@ coWebsiteManager.onResize.subscribe(() => {
});
iframeListener.init();
desktopApi.init();
const app = new App({
target: HtmlUtils.getElementByIdOrFail("game-overlay"),