Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop
This commit is contained in:
commit
b29f649822
4
.github/workflows/continuous_integration.yml
vendored
4
.github/workflows/continuous_integration.yml
vendored
@ -61,6 +61,10 @@ jobs:
|
||||
run: yarn run lint
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Pretty"
|
||||
run: yarn run pretty
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Jasmine"
|
||||
run: yarn test
|
||||
working-directory: "front"
|
||||
|
@ -40,7 +40,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||
"dependencies": {
|
||||
"@workadventure/tiled-map-type-guard": "^1.0.0",
|
||||
"@workadventure/tiled-map-type-guard": "^1.0.2",
|
||||
"axios": "^0.21.1",
|
||||
"busboy": "^0.3.1",
|
||||
"circular-json": "^0.5.9",
|
||||
|
@ -1,7 +1,12 @@
|
||||
/**
|
||||
* Handles variables shared between the scripting API and the server.
|
||||
*/
|
||||
import { ITiledMap, ITiledMapObject, ITiledMapObjectLayer } from "@workadventure/tiled-map-type-guard/dist";
|
||||
import {
|
||||
ITiledMap,
|
||||
ITiledMapLayer,
|
||||
ITiledMapObject,
|
||||
ITiledMapObjectLayer,
|
||||
} from "@workadventure/tiled-map-type-guard/dist";
|
||||
import { User } from "_Model/User";
|
||||
import { variablesRepository } from "./Repository/VariablesRepository";
|
||||
import { redisClient } from "./RedisClient";
|
||||
@ -83,8 +88,14 @@ export class VariablesManager {
|
||||
private static findVariablesInMap(map: ITiledMap): Map<string, Variable> {
|
||||
const objects = new Map<string, Variable>();
|
||||
for (const layer of map.layers) {
|
||||
this.recursiveFindVariablesInLayer(layer, objects);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private static recursiveFindVariablesInLayer(layer: ITiledMapLayer, objects: Map<string, Variable>): void {
|
||||
if (layer.type === "objectgroup") {
|
||||
for (const object of (layer as ITiledMapObjectLayer).objects) {
|
||||
for (const object of layer.objects) {
|
||||
if (object.type === "variable") {
|
||||
if (object.template) {
|
||||
console.warn(
|
||||
@ -97,9 +108,11 @@ export class VariablesManager {
|
||||
objects.set(object.name, this.iTiledObjectToVariable(object));
|
||||
}
|
||||
}
|
||||
} else if (layer.type === "group") {
|
||||
for (const innerLayer of layer.layers) {
|
||||
this.recursiveFindVariablesInLayer(innerLayer, objects);
|
||||
}
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private static iTiledObjectToVariable(object: ITiledMapObject): Variable {
|
||||
|
@ -194,10 +194,10 @@
|
||||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@workadventure/tiled-map-type-guard@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.0.tgz#02524602ee8b2688429a1f56df1d04da3fc171ba"
|
||||
integrity sha512-Mc0SE128otQnYlScQWVaQVyu1+CkailU/FTBh09UTrVnBAhyMO+jIn9vT9+Dv244xq+uzgQDpXmiVdjgrYFQ+A==
|
||||
"@workadventure/tiled-map-type-guard@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.2.tgz#4171550f6cd71be19791faef48360d65d698bcb0"
|
||||
integrity sha512-RCtygGV5y9cb7QoyGMINBE9arM5pyXjkxvXgA5uXEv4GDbXKorhFim/rHgwbVR+eFnVF3rDgWbRnk3DIaHt+lQ==
|
||||
dependencies:
|
||||
generic-type-guard "^3.4.1"
|
||||
|
||||
|
@ -14,7 +14,7 @@ WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subsc
|
||||
WA.state.[any property]: unknown
|
||||
```
|
||||
|
||||
These methods and properties can be used to save, load and track changes in variables related to the current room.
|
||||
These methods and properties can be used to save, load and track changes in [variables related to the current room](variables.md).
|
||||
|
||||
Variables stored in `WA.state` can be any value that is serializable in JSON.
|
||||
|
||||
@ -63,44 +63,11 @@ that you get the expected type).
|
||||
For security reasons, the list of variables you are allowed to access and modify is **restricted** (otherwise, anyone on your map could set any data).
|
||||
Variables storage is subject to an authorization process. Read below to learn more.
|
||||
|
||||
### Declaring allowed keys
|
||||
## Defining a variable
|
||||
|
||||
In order to declare allowed keys related to a room, you need to add **objects** in an "object layer" of the map.
|
||||
|
||||
Each object will represent a variable.
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="images/object_variable.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The name of the variable is the name of the object.
|
||||
The object **type** MUST be **variable**.
|
||||
|
||||
You can set a default value for the object in the `default` property.
|
||||
|
||||
### Persisting variables state
|
||||
|
||||
Use the `persist` property to save the state of the variable in database. If `persist` is false, the variable will stay
|
||||
in the memory of the WorkAdventure servers but will be wiped out of the memory as soon as the room is empty (or if the
|
||||
server restarts).
|
||||
|
||||
{.alert.alert-info}
|
||||
Do not use `persist` for highly dynamic values that have a short life spawn.
|
||||
|
||||
### Managing access rights to variables
|
||||
|
||||
With `readableBy` and `writableBy`, you control who can read of write in this variable. The property accepts a string
|
||||
representing a "tag". Anyone having this "tag" can read/write in the variable.
|
||||
|
||||
{.alert.alert-warning}
|
||||
`readableBy` and `writableBy` are specific to the "online" version of WorkAdventure because the notion of tags
|
||||
is not available unless you have an "admin" server (that is not part of the self-hosted version of WorkAdventure).
|
||||
|
||||
Finally, the `jsonSchema` property can contain [a complete JSON schema](https://json-schema.org/) to validate the content of the variable.
|
||||
Trying to set a variable to a value that is not compatible with the schema will fail.
|
||||
Out of the box, you cannot edit *any* variable. Variables MUST be declared in the map.
|
||||
|
||||
Check the [dedicated variables page](variables.md) to learn how to declare a variable in a map.
|
||||
|
||||
## Tracking variables changes
|
||||
|
||||
|
59
docs/maps/variables.md
Normal file
59
docs/maps/variables.md
Normal file
@ -0,0 +1,59 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# Variables
|
||||
|
||||
Maps can contain **variables**. Variables are piece of information that store some data. In computer science, we like
|
||||
to say variables are storing the "state" of the room.
|
||||
|
||||
- Variables are shared amongst all players in a given room. When the value of a variable changes for one player, it changes
|
||||
for everyone.
|
||||
- Variables are **invisible**. There are plenty of ways they can act on the room, but by default, you don't see them.
|
||||
|
||||
## Declaring a variable
|
||||
|
||||
In order to declare allowed variables in a room, you need to add **objects** in an "object layer" of the map.
|
||||
|
||||
Each object will represent a variable.
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="images/object_variable.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The name of the variable is the name of the object.
|
||||
The object **type** MUST be **variable**.
|
||||
|
||||
You can set a default value for the object in the `default` property.
|
||||
|
||||
## Persisting variables state
|
||||
|
||||
Use the `persist` property to save the state of the variable in database. If `persist` is false, the variable will stay
|
||||
in the memory of the WorkAdventure servers but will be wiped out of the memory as soon as the room is empty (or if the
|
||||
server restarts).
|
||||
|
||||
{.alert.alert-info}
|
||||
Do not use `persist` for highly dynamic values that have a short life spawn.
|
||||
|
||||
## Managing access rights to variables
|
||||
|
||||
With `readableBy` and `writableBy`, you control who can read of write in this variable. The property accepts a string
|
||||
representing a "tag". Anyone having this "tag" can read/write in the variable.
|
||||
|
||||
{.alert.alert-warning}
|
||||
`readableBy` and `writableBy` are specific to the "online" version of WorkAdventure because the notion of tags
|
||||
is not available unless you have an "admin" server (that is not part of the self-hosted version of WorkAdventure).
|
||||
|
||||
In a future release, the `jsonSchema` property will contain [a complete JSON schema](https://json-schema.org/) to validate the content of the variable.
|
||||
Trying to set a variable to a value that is not compatible with the schema will fail.
|
||||
|
||||
## Using variables
|
||||
|
||||
There are plenty of ways to use variables in WorkAdventure:
|
||||
|
||||
- Using the [scripting API](api-state.md), you can read, edit or track the content of variables.
|
||||
- Using the [Action zones](https://workadventu.re/map-building-extra/generic-action-zones.md), you can set the value of a variable when someone is entering or leaving a zone
|
||||
- By [binding variable values to properties in the map](https://workadventu.re/map-building-extra/variable-to-property-binding.md)
|
||||
- By [using automatically generated configuration screens](https://workadventu.re/map-building-extra/automatic-configuration.md) to create forms to edit the value of variables
|
||||
|
||||
In general, variables can be used by third party libraries that you can embed in your map to add extra features.
|
||||
A good example of such a library is the ["Scripting API Extra" library](https://workadventu.re/map-building-extra/about.md)
|
0
front/dist/resources/html/gameMenu.html
vendored
Normal file
0
front/dist/resources/html/gameMenu.html
vendored
Normal file
@ -1,10 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isButtonClickedEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isButtonClickedEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
popupId: tg.isNumber,
|
||||
buttonId: tg.isNumber,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||
*/
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isChatEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isChatEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
message: tg.isString,
|
||||
author: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isClosePopupEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isClosePopupEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
popupId: tg.isNumber,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isEnterLeaveEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isEnterLeaveEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
name: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||
*/
|
||||
|
@ -1,11 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isGoToPageEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isGoToPageEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isLoadSoundEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isLoadSoundEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,18 +1,20 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
const isButtonDescriptor =
|
||||
new tg.IsInterface().withProperties({
|
||||
const isButtonDescriptor = new tg.IsInterface()
|
||||
.withProperties({
|
||||
label: tg.isString,
|
||||
className: tg.isOptional(tg.isString)
|
||||
}).get();
|
||||
className: tg.isOptional(tg.isString),
|
||||
})
|
||||
.get();
|
||||
|
||||
export const isOpenPopupEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isOpenPopupEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
popupId: tg.isNumber,
|
||||
targetObject: tg.isString,
|
||||
message: tg.isString,
|
||||
buttons: tg.isArray(isButtonDescriptor)
|
||||
}).get();
|
||||
buttons: tg.isArray(isButtonDescriptor),
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,11 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isOpenTabEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isOpenTabEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,22 +1,23 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
const isSoundConfig =
|
||||
new tg.IsInterface().withProperties({
|
||||
const isSoundConfig = new tg.IsInterface()
|
||||
.withProperties({
|
||||
volume: tg.isOptional(tg.isNumber),
|
||||
loop: tg.isOptional(tg.isBoolean),
|
||||
mute: tg.isOptional(tg.isBoolean),
|
||||
rate: tg.isOptional(tg.isNumber),
|
||||
detune: tg.isOptional(tg.isNumber),
|
||||
seek: tg.isOptional(tg.isNumber),
|
||||
delay: tg.isOptional(tg.isNumber)
|
||||
}).get();
|
||||
delay: tg.isOptional(tg.isNumber),
|
||||
})
|
||||
.get();
|
||||
|
||||
export const isPlaySoundEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isPlaySoundEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
url: tg.isString,
|
||||
config: tg.isOptional(isSoundConfig),
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isStopSoundEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isStopSoundEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isUserInputChatEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
export const isUserInputChatEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
message: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
* A message sent from the game to the iFrame when a user types a message in the chat.
|
||||
*/
|
||||
|
@ -6,13 +6,13 @@ export interface ButtonDescriptor {
|
||||
/**
|
||||
* The label of the button
|
||||
*/
|
||||
label: string,
|
||||
label: string;
|
||||
/**
|
||||
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
|
||||
*/
|
||||
className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled",
|
||||
className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled";
|
||||
/**
|
||||
* Callback called if the button is pressed
|
||||
*/
|
||||
callback: ButtonClickedCallback,
|
||||
callback: ButtonClickedCallback;
|
||||
}
|
||||
|
@ -2,18 +2,17 @@ import {sendToWorkadventure} from "../IframeApiContribution";
|
||||
import type { ClosePopupEvent } from "../../Events/ClosePopupEvent";
|
||||
|
||||
export class Popup {
|
||||
constructor(private id: number) {
|
||||
}
|
||||
constructor(private id: number) {}
|
||||
|
||||
/**
|
||||
* Closes the popup
|
||||
*/
|
||||
public close(): void {
|
||||
sendToWorkadventure({
|
||||
'type': 'closePopup',
|
||||
'data': {
|
||||
'popupId': this.id,
|
||||
} as ClosePopupEvent
|
||||
type: "closePopup",
|
||||
data: {
|
||||
popupId: this.id,
|
||||
} as ClosePopupEvent,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,17 @@ import type {IframeResponseEventMap} from "../../Api/Events/IframeEvent";
|
||||
import type { IframeCallback } from "../../Api/iframe/IframeApiContribution";
|
||||
import type { IframeCallbackContribution } from "../../Api/iframe/IframeApiContribution";
|
||||
|
||||
export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: IframeCallback<K> } = {}
|
||||
export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: IframeCallback<K> } = {};
|
||||
|
||||
export function apiCallback<T extends keyof IframeResponseEventMap>(callbackData: IframeCallbackContribution<T>): IframeCallbackContribution<keyof IframeResponseEventMap> {
|
||||
export function apiCallback<T extends keyof IframeResponseEventMap>(
|
||||
callbackData: IframeCallbackContribution<T>
|
||||
): IframeCallbackContribution<keyof IframeResponseEventMap> {
|
||||
const iframeCallback = {
|
||||
typeChecker: callbackData.typeChecker,
|
||||
callback: callbackData.callback
|
||||
callback: callbackData.callback,
|
||||
} as IframeCallback<T>;
|
||||
|
||||
const newCallback = { [callbackData.type]: iframeCallback };
|
||||
Object.assign(registeredCallbacks, newCallback)
|
||||
Object.assign(registeredCallbacks, newCallback);
|
||||
return callbackData as unknown as IframeCallbackContribution<keyof IframeResponseEventMap>;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import audioImg from "../images/audio.svg";
|
||||
import audioMuteImg from "../images/audio-mute.svg";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import type { audioManagerVolume } from "../../Stores/AudioManagerStore";
|
||||
import {
|
||||
@ -12,6 +10,8 @@
|
||||
import {onDestroy, onMount} from "svelte";
|
||||
|
||||
let HTMLAudioPlayer: HTMLAudioElement;
|
||||
let audioPlayerVolumeIcon: HTMLElement;
|
||||
let audioPlayerVol: HTMLInputElement;
|
||||
let unsubscriberFileStore: Unsubscriber | null = null;
|
||||
let unsubscriberVolumeStore: Unsubscriber | null = null;
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
let decreaseWhileTalking: boolean = true;
|
||||
|
||||
onMount(() => {
|
||||
volume = localUserStore.getAudioPlayerVolume();
|
||||
audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted());
|
||||
setVolume();
|
||||
|
||||
unsubscriberFileStore = audioManagerFileStore.subscribe(() =>{
|
||||
HTMLAudioPlayer.pause();
|
||||
HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop;
|
||||
@ -52,9 +56,26 @@
|
||||
function onMute() {
|
||||
audioManagerVolumeStore.setMuted(!get(audioManagerVolumeStore).muted);
|
||||
localUserStore.setAudioPlayerMuted(get(audioManagerVolumeStore).muted);
|
||||
setVolume();
|
||||
}
|
||||
|
||||
function setVolume() {
|
||||
if (get(audioManagerVolumeStore).muted) {
|
||||
audioPlayerVolumeIcon.classList.add('muted');
|
||||
audioPlayerVol.value = "0";
|
||||
} else {
|
||||
audioPlayerVol.value = "" + volume;
|
||||
audioPlayerVolumeIcon.classList.remove('muted');
|
||||
if (volume < 0.3) {
|
||||
audioPlayerVolumeIcon.classList.add('low');
|
||||
} else if (volume < 0.7) {
|
||||
audioPlayerVolumeIcon.classList.remove('low');
|
||||
audioPlayerVolumeIcon.classList.add('mid');
|
||||
} else {
|
||||
audioPlayerVolumeIcon.classList.remove('low');
|
||||
audioPlayerVolumeIcon.classList.remove('mid');
|
||||
}
|
||||
}
|
||||
audioManagerVolumeStore.setVolume(volume)
|
||||
localUserStore.setAudioPlayerVolume(get(audioManagerVolumeStore).volume);
|
||||
}
|
||||
@ -67,8 +88,28 @@
|
||||
|
||||
<div class="main-audio-manager nes-container is-rounded">
|
||||
<div class="audio-manager-player-volume">
|
||||
<img src={$audioManagerVolumeStore.muted ? audioMuteImg : audioImg} alt="player volume" on:click={onMute}>
|
||||
<input type="range" min="0" max="1" step="0.025" bind:value={volume} on:change={setVolume}>
|
||||
<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">
|
||||
<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:value={volume} bind:this={audioPlayerVol}
|
||||
on:change={setVolume}>
|
||||
</div>
|
||||
<div class="audio-manager-reduce-conversation">
|
||||
<label>
|
||||
@ -105,7 +146,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr;
|
||||
|
||||
img {
|
||||
span svg {
|
||||
height: 100%;
|
||||
width: calc(100% - 10px);
|
||||
margin-right: 10px;
|
||||
@ -115,5 +156,37 @@
|
||||
section.audio-manager-file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#audioplayer_volume_icon_playing.muted {
|
||||
#audioplayer_volume_icon_playing_low {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#audioplayer_volume_icon_playing_mid {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#audioplayer_volume_icon_playing_high {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#audioplayer_volume_icon_playing.low #audioplayer_volume_icon_playing_high {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#audioplayer_volume_icon_playing.low #audioplayer_volume_icon_playing_mid {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#audioplayer_volume_icon_playing.mid #audioplayer_volume_icon_playing_high {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
<script lang="typescript">
|
||||
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||
import {isSilentStore, requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||
import monitorImg from "./images/monitor.svg";
|
||||
import monitorCloseImg from "./images/monitor-close.svg";
|
||||
import cinemaImg from "./images/cinema.svg";
|
||||
@ -12,6 +12,7 @@
|
||||
import {layoutModeStore} from "../Stores/StreamableCollectionStore";
|
||||
import {LayoutMode} from "../WebRtc/LayoutManager";
|
||||
import {peerStore} from "../Stores/PeerStore";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if ($requestedScreenSharingState === true) {
|
||||
@ -44,6 +45,12 @@
|
||||
$layoutModeStore = LayoutMode.Presentation;
|
||||
}
|
||||
}
|
||||
|
||||
let isSilent: boolean;
|
||||
const unsubscribeIsSilent = isSilentStore.subscribe(value => {
|
||||
isSilent = value;
|
||||
});
|
||||
onDestroy(unsubscribeIsSilent);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@ -55,22 +62,22 @@
|
||||
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState}
|
||||
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore || isSilent} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState && !isSilent}
|
||||
<img src={monitorImg} alt="Start screen sharing">
|
||||
{:else}
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}>
|
||||
{#if $requestedCameraState}
|
||||
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
|
||||
{#if $requestedCameraState && !isSilent}
|
||||
<img src={cinemaImg} alt="Turn on webcam">
|
||||
{:else}
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
|
||||
{#if $requestedMicrophoneState}
|
||||
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
||||
{#if $requestedMicrophoneState && !isSilent}
|
||||
<img src={microphoneImg} alt="Turn on microphone">
|
||||
{:else}
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||
|
@ -1,13 +1,17 @@
|
||||
<script lang="typescript">
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
import {menuIconVisiblilityStore, menuVisiblilityStore} from "../../Stores/MenuStore";
|
||||
import {menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected} from "../../Stores/MenuStore";
|
||||
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
|
||||
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
|
||||
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||
//import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {PROFILE_URL} from "../../Enum/EnvironmentVariable";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {enableCameraSceneVisibilityStore} from "../../Stores/MediaStore";
|
||||
|
||||
|
||||
function disableMenuStores(){
|
||||
@ -33,22 +37,50 @@
|
||||
gameManager.leaveGame(SelectCharacterSceneName,new SelectCharacterScene());
|
||||
}
|
||||
|
||||
function logOut(){
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
connectionManager.logout();
|
||||
}
|
||||
|
||||
function getProfileUrl(){
|
||||
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
|
||||
}
|
||||
|
||||
function openEnableCameraScene(){
|
||||
disableMenuStores();
|
||||
enableCameraSceneVisibilityStore.showEnableCameraScene();
|
||||
gameManager.leaveGame(EnableCameraSceneName,new EnableCameraScene());
|
||||
}
|
||||
|
||||
//TODO: Uncomment when login will be completely developed
|
||||
/*function clickLogin() {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
}*/
|
||||
|
||||
</script>
|
||||
|
||||
<div class="customize-main">
|
||||
{#if $userIsConnected}
|
||||
<section>
|
||||
{#if PROFILE_URL != undefined}
|
||||
<iframe title="profile" src="{getProfileUrl()}"></iframe>
|
||||
{/if}
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}>Log out</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section>
|
||||
<a type="button" class="nes-btn" href="/login">Sing in</a>
|
||||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button>
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn is-rounded" on:click|preventDefault={openEditSkinScene}>Edit Skin</button>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>Setup camera</button>
|
||||
</section>
|
||||
<!-- <section>
|
||||
<button type="button" class="nes-btn is-primary" on:click|preventDefault={clickLogin}>Login</button>
|
||||
@ -63,6 +95,12 @@
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
iframe{
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 50px;
|
||||
width: 250px;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="typescript">
|
||||
import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||
import {localStreamStore} from "../Stores/MediaStore";
|
||||
import {localStreamStore, isSilentStore} from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import {srcObject} from "./Video/utils";
|
||||
@ -17,14 +17,25 @@
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
|
||||
let isSilent: boolean;
|
||||
const unsubscribeIsSilent = isSilentStore.subscribe(value => {
|
||||
isSilent = value;
|
||||
});
|
||||
|
||||
onDestroy(unsubscribeIsSilent);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="video-container div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video}>
|
||||
<div class="video-container div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
|
||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video>
|
||||
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="is-silent" class:hide={isSilent}>
|
||||
Silent zone
|
||||
</div>
|
||||
</div>
|
||||
|
@ -12,7 +12,9 @@
|
||||
|
||||
<div class="main-section">
|
||||
{#if $videoFocusStore }
|
||||
{#key $videoFocusStore.uniqueId}
|
||||
<MediaBox streamable={$videoFocusStore}></MediaBox>
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
<aside class="sidebar">
|
||||
|
@ -2,14 +2,14 @@ import {Subject} from "rxjs";
|
||||
import type { BanUserMessage, SendUserMessage } from "../Messages/generated/messages_pb";
|
||||
|
||||
export enum AdminMessageEventTypes {
|
||||
admin = 'message',
|
||||
audio = 'audio',
|
||||
ban = 'ban',
|
||||
banned = 'banned',
|
||||
admin = "message",
|
||||
audio = "audio",
|
||||
ban = "ban",
|
||||
banned = "banned",
|
||||
}
|
||||
|
||||
interface AdminMessageEvent {
|
||||
type: AdminMessageEventTypes,
|
||||
type: AdminMessageEventTypes;
|
||||
text: string;
|
||||
//todo add optional properties for other event types
|
||||
}
|
||||
@ -21,14 +21,14 @@ class AdminMessagesService {
|
||||
public messageStream = this._messageStream.asObservable();
|
||||
|
||||
constructor() {
|
||||
this.messageStream.subscribe((event) => console.log('message', event))
|
||||
this.messageStream.subscribe((event) => console.log("message", event));
|
||||
}
|
||||
|
||||
onSendusermessage(message: SendUserMessage | BanUserMessage) {
|
||||
this._messageStream.next({
|
||||
type: message.getType() as unknown as AdminMessageEventTypes,
|
||||
text: message.getMessage(),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ import { localUserStore } from "./LocalUserStore";
|
||||
import { CharacterTexture, LocalUser } from "./LocalUser";
|
||||
import { Room } from "./Room";
|
||||
import { _ServiceWorker } from "../Network/ServiceWorker";
|
||||
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
|
||||
import { userIsConnected } from "../Stores/MenuStore";
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!: LocalUser;
|
||||
@ -15,6 +17,7 @@ class ConnectionManager {
|
||||
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
||||
private _unloading: boolean = false;
|
||||
private authToken: string | null = null;
|
||||
private _currentRoom: Room | null = null;
|
||||
|
||||
private serviceWorker?: _ServiceWorker;
|
||||
|
||||
@ -30,28 +33,39 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Promise<void>
|
||||
* TODO fix me to be move in game manager
|
||||
*/
|
||||
public loadOpenIDScreen(): Promise<void> {
|
||||
public loadOpenIDScreen() {
|
||||
const state = localUserStore.generateState();
|
||||
const nonce = localUserStore.generateNonce();
|
||||
localUserStore.setAuthToken(null);
|
||||
|
||||
//TODO refactor this and don't realise previous call
|
||||
return Axios.get(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`)
|
||||
.then(() => {
|
||||
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err, "We don't have URL to regenerate authentication user");
|
||||
//TODO show modal login
|
||||
window.location.reload();
|
||||
});
|
||||
//TODO fix me to redirect this URL by pusher
|
||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
}
|
||||
const redirectUrl = `${this._currentRoom.iframeAuthentication}?state=${state}&nonce=${nonce}`;
|
||||
window.location.assign(redirectUrl);
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
public logout() {
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
public async logout() {
|
||||
//user logout, set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
//Logout user in pusher and hydra
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(null);
|
||||
window.location.reload();
|
||||
|
||||
//Go on login page can permit to clear token and start authentication process
|
||||
window.location.assign("/login");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,8 +74,13 @@ class ConnectionManager {
|
||||
public async initGameConnexion(): Promise<Room> {
|
||||
const connexionType = urlManager.getGameConnexionType();
|
||||
this.connexionType = connexionType;
|
||||
let room: Room | null = null;
|
||||
if (connexionType === GameConnexionTypes.jwt) {
|
||||
this._currentRoom = null;
|
||||
if (connexionType === GameConnexionTypes.login) {
|
||||
//TODO clear all cash and redirect on login scene (iframe)
|
||||
localUserStore.setAuthToken(null);
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
@ -71,14 +90,15 @@ class ConnectionManager {
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.authToken = authToken;
|
||||
room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
localUserStore.setCode(code);
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadOpenIDScreen();
|
||||
}
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.register) {
|
||||
//@deprecated
|
||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||
@ -92,7 +112,7 @@ class ConnectionManager {
|
||||
|
||||
const roomUrl = data.roomUrl;
|
||||
|
||||
room = await Room.createRoom(
|
||||
this._currentRoom = await Room.createRoom(
|
||||
new URL(
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
@ -102,7 +122,7 @@ class ConnectionManager {
|
||||
window.location.hash
|
||||
)
|
||||
);
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (
|
||||
connexionType === GameConnexionTypes.organization ||
|
||||
connexionType === GameConnexionTypes.anonymous ||
|
||||
@ -112,12 +132,18 @@ class ConnectionManager {
|
||||
//todo: add here some kind of warning if authToken has expired.
|
||||
if (!this.authToken) {
|
||||
await this.anonymousLogin();
|
||||
} else {
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||
|
||||
let roomPath: string;
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
roomPath = window.location.protocol + "//" + window.location.host + START_ROOM_URL;
|
||||
roomPath = localUserStore.getLastRoomUrl();
|
||||
//get last room path from cache api
|
||||
try {
|
||||
const lastRoomUrl = await localUserStore.getLastRoomUrlCacheApi();
|
||||
@ -138,13 +164,13 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
//get detail map for anonymous login and set texture in local storage
|
||||
room = await Room.createRoom(new URL(roomPath));
|
||||
if (room.textures != undefined && room.textures.length > 0) {
|
||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if (this.localUser.textures.length === 0) {
|
||||
this.localUser.textures = room.textures;
|
||||
this.localUser.textures = this._currentRoom.textures;
|
||||
} else {
|
||||
room.textures.forEach((newTexture) => {
|
||||
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;
|
||||
@ -155,12 +181,12 @@ class ConnectionManager {
|
||||
localUserStore.saveUser(this.localUser);
|
||||
}
|
||||
}
|
||||
if (room == undefined) {
|
||||
if (this._currentRoom == undefined) {
|
||||
return Promise.reject(new Error("Invalid URL"));
|
||||
}
|
||||
|
||||
this.serviceWorker = new _ServiceWorker();
|
||||
return Promise.resolve(room);
|
||||
return Promise.resolve(this._currentRoom);
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
@ -215,9 +241,6 @@ class ConnectionManager {
|
||||
});
|
||||
|
||||
connection.onConnect((connect: OnConnectInterface) => {
|
||||
//save last room url connected
|
||||
localUserStore.setLastRoomUrl(roomUrl);
|
||||
|
||||
resolve(connect);
|
||||
});
|
||||
}).catch((err) => {
|
||||
@ -237,6 +260,34 @@ class ConnectionManager {
|
||||
get getConnexionType() {
|
||||
return this.connexionType;
|
||||
}
|
||||
|
||||
async checkAuthUserConnexion() {
|
||||
//set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
const state = localUserStore.getState();
|
||||
const code = localUserStore.getCode();
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.authToken = authToken;
|
||||
|
||||
//user connected, set connected store for menu at true
|
||||
userIsConnected.set(true);
|
||||
}
|
||||
|
||||
get currentRoom() {
|
||||
return this._currentRoom;
|
||||
}
|
||||
}
|
||||
|
||||
export const connectionManager = new ConnectionManager();
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
interface EmoteEvent {
|
||||
userId: number,
|
||||
emoteName: string,
|
||||
userId: number;
|
||||
emoteName: string;
|
||||
}
|
||||
|
||||
class EmoteEventStream {
|
||||
|
||||
private _stream: Subject<EmoteEvent> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
fire(userId: number, emoteName: string) {
|
||||
this._stream.next({ userId, emoteName });
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { START_ROOM_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const playerNameKey = "playerName";
|
||||
const selectedPlayerKey = "selectedPlayer";
|
||||
@ -17,6 +18,8 @@ const authToken = "authToken";
|
||||
const state = "state";
|
||||
const nonce = "nonce";
|
||||
const notification = "notificationPermission";
|
||||
const code = "code";
|
||||
const cameraSetup = "cameraSetup";
|
||||
|
||||
const cacheAPIIndex = "workavdenture-cache";
|
||||
|
||||
@ -125,7 +128,9 @@ class LocalUserStore {
|
||||
});
|
||||
}
|
||||
getLastRoomUrl(): string {
|
||||
return localStorage.getItem(lastRoomUrl) ?? "";
|
||||
return (
|
||||
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
|
||||
);
|
||||
}
|
||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||
return caches.open(cacheAPIIndex).then((cache) => {
|
||||
@ -160,19 +165,32 @@ class LocalUserStore {
|
||||
|
||||
verifyState(value: string): boolean {
|
||||
const oldValue = localStorage.getItem(state);
|
||||
localStorage.removeItem(state);
|
||||
return oldValue === value;
|
||||
}
|
||||
getState(): string | null {
|
||||
return localStorage.getItem(state);
|
||||
}
|
||||
generateNonce(): string {
|
||||
const newNonce = uuidv4();
|
||||
localStorage.setItem(nonce, newNonce);
|
||||
return newNonce;
|
||||
}
|
||||
|
||||
getNonce(): string | null {
|
||||
const oldValue = localStorage.getItem(nonce);
|
||||
localStorage.removeItem(nonce);
|
||||
return oldValue;
|
||||
return localStorage.getItem(nonce);
|
||||
}
|
||||
setCode(value: string): void {
|
||||
localStorage.setItem(code, value);
|
||||
}
|
||||
getCode(): string | null {
|
||||
return localStorage.getItem(code);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ export interface RoomRedirect {
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private _authenticationMandatory: boolean = false;
|
||||
private _iframeAuthentication?: string;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
private instance: string | undefined;
|
||||
@ -101,6 +103,8 @@ export class Room {
|
||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
||||
this._iframeAuthentication = data.iframeAuthentication;
|
||||
return new MapDetail(data.mapUrl, data.textures);
|
||||
}
|
||||
|
||||
@ -186,4 +190,12 @@ export class Room {
|
||||
}
|
||||
return this._mapUrl;
|
||||
}
|
||||
|
||||
get authenticationMandatory(): boolean {
|
||||
return this._authenticationMandatory;
|
||||
}
|
||||
|
||||
get iframeAuthentication(): string | undefined {
|
||||
return this._iframeAuthentication;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,11 @@ export class RoomConnection implements RoomConnection {
|
||||
*
|
||||
* @param token A JWT token containing the email of the user
|
||||
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
|
||||
* @param name
|
||||
* @param characterLayers
|
||||
* @param position
|
||||
* @param viewport
|
||||
* @param companion
|
||||
*/
|
||||
public constructor(
|
||||
token: string | null,
|
||||
@ -218,7 +223,7 @@ export class RoomConnection implements RoomConnection {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasTokenexpiredmessage()) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
connectionManager.logout();
|
||||
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
class WorldFullMessageStream {
|
||||
|
||||
private _stream: Subject<string | null> = new Subject<string | null>();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage(message?: string) {
|
||||
this._stream.next(message);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
||||
export const NODE_ENV = process.env.NODE_ENV || "development";
|
||||
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
||||
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
export class MessageUI {
|
||||
|
||||
static warningMessage(text: string) {
|
||||
this.removeMessage();
|
||||
const body = document.getElementById("body");
|
||||
body?.insertAdjacentHTML('afterbegin', `
|
||||
body?.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
`
|
||||
<div id="message-reconnect" class="message-info warning">
|
||||
${text}
|
||||
</div>
|
||||
`);
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
static removeMessage(id: string | null = null) {
|
||||
|
@ -3,21 +3,20 @@ import Direction = PositionMessage.Direction;
|
||||
import type { PointInterface } from "../Connexion/ConnexionModels";
|
||||
|
||||
export class ProtobufClientUtils {
|
||||
|
||||
public static toPointInterface(position: PositionMessage): PointInterface {
|
||||
let direction: string;
|
||||
switch (position.getDirection()) {
|
||||
case Direction.UP:
|
||||
direction = 'up';
|
||||
direction = "up";
|
||||
break;
|
||||
case Direction.DOWN:
|
||||
direction = 'down';
|
||||
direction = "down";
|
||||
break;
|
||||
case Direction.LEFT:
|
||||
direction = 'left';
|
||||
direction = "left";
|
||||
break;
|
||||
case Direction.RIGHT:
|
||||
direction = 'right';
|
||||
direction = "right";
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unexpected direction");
|
||||
|
@ -16,7 +16,7 @@ export class Companion extends Container {
|
||||
private delta: number;
|
||||
private invisible: boolean;
|
||||
private updateListener: Function;
|
||||
private target: { x: number, y: number, direction: PlayerAnimationDirections };
|
||||
private target: { x: number; y: number; direction: PlayerAnimationDirections };
|
||||
|
||||
private companionName: string;
|
||||
private direction: PlayerAnimationDirections;
|
||||
@ -36,10 +36,10 @@ export class Companion extends Container {
|
||||
|
||||
this.companionName = name;
|
||||
|
||||
texturePromise.then(resource => {
|
||||
texturePromise.then((resource) => {
|
||||
this.addResource(resource);
|
||||
this.invisible = false;
|
||||
})
|
||||
});
|
||||
|
||||
this.scene.physics.world.enableBody(this);
|
||||
|
||||
@ -52,7 +52,7 @@ export class Companion extends Container {
|
||||
this.setDepth(-1);
|
||||
|
||||
this.updateListener = this.step.bind(this);
|
||||
this.scene.events.addListener('update', this.updateListener);
|
||||
this.scene.events.addListener("update", this.updateListener);
|
||||
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
@ -62,7 +62,7 @@ export class Companion extends Container {
|
||||
}
|
||||
|
||||
public step(time: number, delta: number) {
|
||||
if (typeof this.target === 'undefined') return;
|
||||
if (typeof this.target === "undefined") return;
|
||||
|
||||
this.delta += delta;
|
||||
if (this.delta < 128) {
|
||||
@ -87,7 +87,10 @@ export class Companion extends Container {
|
||||
const yDir = yDist / Math.max(Math.abs(yDist), 1);
|
||||
|
||||
const speed = 256;
|
||||
this.getBody().setVelocity(Math.min(Math.abs(xDist * 2.5), speed) * xDir, Math.min(Math.abs(yDist * 2.5), speed) * yDir);
|
||||
this.getBody().setVelocity(
|
||||
Math.min(Math.abs(xDist * 2.5), speed) * xDir,
|
||||
Math.min(Math.abs(yDist * 2.5), speed) * yDir
|
||||
);
|
||||
|
||||
if (Math.abs(xDist) > Math.abs(yDist)) {
|
||||
if (xDist < 0) {
|
||||
@ -116,8 +119,8 @@ export class Companion extends Container {
|
||||
y,
|
||||
direction,
|
||||
moving: animationType === PlayerAnimationTypes.Walk,
|
||||
name: companionName
|
||||
}
|
||||
name: companionName,
|
||||
};
|
||||
}
|
||||
|
||||
private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void {
|
||||
@ -133,7 +136,7 @@ export class Companion extends Container {
|
||||
|
||||
this.add(sprite);
|
||||
|
||||
this.getAnimations(resource).forEach(animation => {
|
||||
this.getAnimations(resource).forEach((animation) => {
|
||||
this.scene.anims.create(animation);
|
||||
});
|
||||
|
||||
@ -147,58 +150,58 @@ export class Companion extends Container {
|
||||
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [1] }),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [4] }),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [7] }),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [10] }),
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [0, 1, 2] }),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [3, 4, 5] }),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [6, 7, 8] }),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frames: this.scene.anims.generateFrameNumbers(resource, { frames: [9, 10, 11] }),
|
||||
frameRate: 15,
|
||||
repeat: -1
|
||||
}
|
||||
]
|
||||
repeat: -1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private getBody(): Phaser.Physics.Arcade.Body {
|
||||
const body = this.body;
|
||||
|
||||
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
|
||||
throw new Error('Container does not have arcade body');
|
||||
throw new Error("Container does not have arcade body");
|
||||
}
|
||||
|
||||
return body;
|
||||
@ -212,7 +215,7 @@ export class Companion extends Container {
|
||||
}
|
||||
|
||||
if (this.scene) {
|
||||
this.scene.events.removeListener('update', this.updateListener);
|
||||
this.scene.events.removeListener("update", this.updateListener);
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
|
@ -1,7 +1,7 @@
|
||||
export interface CompanionResourceDescriptionInterface {
|
||||
name: string,
|
||||
img: string,
|
||||
behaviour: "dog" | "cat"
|
||||
name: string;
|
||||
img: string;
|
||||
behaviour: "dog" | "cat";
|
||||
}
|
||||
|
||||
export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [
|
||||
@ -11,4 +11,4 @@ export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [
|
||||
{ name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" },
|
||||
{ name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" },
|
||||
{ name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" },
|
||||
]
|
||||
];
|
||||
|
@ -7,13 +7,13 @@ export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourc
|
||||
});
|
||||
|
||||
return COMPANION_RESOURCES;
|
||||
}
|
||||
};
|
||||
|
||||
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const resource = COMPANION_RESOURCES.find(item => item.name === name);
|
||||
const resource = COMPANION_RESOURCES.find((item) => item.name === name);
|
||||
|
||||
if (typeof resource === 'undefined') {
|
||||
if (typeof resource === "undefined") {
|
||||
return reject(`Texture '${name}' not found!`);
|
||||
}
|
||||
|
||||
@ -26,4 +26,4 @@ export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): P
|
||||
|
||||
loader.start(); // It's only automatically started during the Scene preload.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||
|
||||
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||
super(scene, x, y, 'layout_modes', 3);
|
||||
super(scene, x, y, "layout_modes", 3);
|
||||
scene.add.existing(this);
|
||||
this.setScrollFactor(0, 0);
|
||||
this.setOrigin(0, 1);
|
||||
|
@ -1,11 +1,8 @@
|
||||
|
||||
export class ClickButton extends Phaser.GameObjects.Image {
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, textureName: string, callback: Function) {
|
||||
super(scene, x, y, textureName);
|
||||
this.scene.add.existing(this);
|
||||
this.setInteractive();
|
||||
this.on("pointerup", callback);
|
||||
}
|
||||
|
||||
}
|
@ -70,3 +70,9 @@ export const addLoader = (scene: Phaser.Scene): void => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const removeLoader = (scene: Phaser.Scene): void => {
|
||||
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
||||
scene.load.textureManager.remove(LogoNameIndex);
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||
|
||||
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||
super(scene, x, y, 'layout_modes', 0);
|
||||
super(scene, x, y, "layout_modes", 0);
|
||||
scene.add.existing(this);
|
||||
this.setScrollFactor(0, 0);
|
||||
this.setOrigin(0, 1);
|
||||
|
@ -3,18 +3,18 @@ import {DEPTH_UI_INDEX} from "../Game/DepthIndexes";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
|
||||
export interface RadialMenuItem {
|
||||
image: string,
|
||||
name: string,
|
||||
image: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const RadialMenuClickEvent = 'radialClick';
|
||||
export const RadialMenuClickEvent = "radialClick";
|
||||
|
||||
export class RadialMenu extends Phaser.GameObjects.Container {
|
||||
private resizeCallback: OmitThisParameter<() => void>;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, private items: RadialMenuItem[]) {
|
||||
super(scene, x, y);
|
||||
this.setDepth(DEPTH_UI_INDEX)
|
||||
this.setDepth(DEPTH_UI_INDEX);
|
||||
this.scene.add.existing(this);
|
||||
this.initItems();
|
||||
|
||||
@ -26,7 +26,7 @@ export class RadialMenu extends Phaser.GameObjects.Container {
|
||||
private initItems() {
|
||||
const itemsNumber = this.items.length;
|
||||
const menuRadius = 70 + (waScaleManager.uiScalingFactor - 1) * 20;
|
||||
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius))
|
||||
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius));
|
||||
}
|
||||
|
||||
private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number, menuRadius: number) {
|
||||
@ -34,32 +34,32 @@ export class RadialMenu extends Phaser.GameObjects.Container {
|
||||
this.add(image);
|
||||
this.scene.sys.updateList.add(image);
|
||||
const scalingFactor = waScaleManager.uiScalingFactor * 0.075;
|
||||
image.setScale(scalingFactor)
|
||||
image.setScale(scalingFactor);
|
||||
image.setInteractive({
|
||||
useHandCursor: true,
|
||||
});
|
||||
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
|
||||
image.on('pointerover', () => {
|
||||
image.on("pointerdown", () => this.emit(RadialMenuClickEvent, item));
|
||||
image.on("pointerover", () => {
|
||||
this.scene.tweens.add({
|
||||
targets: image,
|
||||
props: {
|
||||
scale: 2 * scalingFactor,
|
||||
},
|
||||
duration: 500,
|
||||
ease: 'Power3',
|
||||
})
|
||||
ease: "Power3",
|
||||
});
|
||||
image.on('pointerout', () => {
|
||||
});
|
||||
image.on("pointerout", () => {
|
||||
this.scene.tweens.add({
|
||||
targets: image,
|
||||
props: {
|
||||
scale: scalingFactor,
|
||||
},
|
||||
duration: 500,
|
||||
ease: 'Power3',
|
||||
})
|
||||
ease: "Power3",
|
||||
});
|
||||
const angle = 2 * Math.PI * index / itemsNumber;
|
||||
});
|
||||
const angle = (2 * Math.PI * index) / itemsNumber;
|
||||
Phaser.Actions.RotateAroundDistance([image], { x: 0, y: 0 }, angle, menuRadius);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context';
|
||||
import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
|
||||
|
||||
/**
|
||||
* Class to measure the sound volume of a media stream
|
||||
@ -27,8 +27,7 @@ export class SoundMeter {
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
public connectToSource(stream: MediaStream, context: IAudioContext): void
|
||||
{
|
||||
public connectToSource(stream: MediaStream, context: IAudioContext): void {
|
||||
if (this.source !== undefined) {
|
||||
this.stop();
|
||||
}
|
||||
@ -42,8 +41,6 @@ export class SoundMeter {
|
||||
//analyser.connect(distortion);
|
||||
//distortion.connect(this.context.destination);
|
||||
//this.analyser.connect(this.context.destination);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public getVolume(): number {
|
||||
@ -52,7 +49,6 @@ export class SoundMeter {
|
||||
}
|
||||
this.analyser.getByteFrequencyData(this.dataArray);
|
||||
|
||||
|
||||
const input = this.dataArray;
|
||||
let i;
|
||||
let sum = 0.0;
|
||||
@ -84,6 +80,4 @@ export class SoundMeter {
|
||||
this.dataArray = undefined;
|
||||
this.source = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
|
||||
export class TextField extends Phaser.GameObjects.BitmapText {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, text: string | string[], center: boolean = true) {
|
||||
super(scene, x, y, 'main_font', text, 8);
|
||||
super(scene, x, y, "main_font", text, 8);
|
||||
this.scene.add.existing(this);
|
||||
if (center) {
|
||||
this.setOrigin(0.5).setCenterAlign()
|
||||
this.setOrigin(0.5).setCenterAlign();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import type {GameScene} from "../Game/GameScene";
|
||||
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
import { isSilentStore } from "../../Stores/MediaStore";
|
||||
|
||||
const playerNameY = -25;
|
||||
|
||||
@ -17,7 +18,7 @@ interface AnimationData {
|
||||
frameRate: number;
|
||||
repeat: number;
|
||||
frameModel: string; //todo use an enum
|
||||
frames : number[]
|
||||
frames: number[];
|
||||
}
|
||||
|
||||
const interactiveRadius = 35;
|
||||
@ -35,7 +36,8 @@ export abstract class Character extends Container {
|
||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||
scene: GameScene;
|
||||
|
||||
constructor(scene: GameScene,
|
||||
constructor(
|
||||
scene: GameScene,
|
||||
x: number,
|
||||
y: number,
|
||||
texturesPromise: Promise<string[]>,
|
||||
@ -50,17 +52,22 @@ export abstract class Character extends Container {
|
||||
super(scene, x, y /*, texture, frame*/);
|
||||
this.scene = scene;
|
||||
this.PlayerValue = name;
|
||||
this.invisible = true
|
||||
this.invisible = true;
|
||||
|
||||
this.sprites = new Map<string, Sprite>();
|
||||
|
||||
//textures are inside a Promise in case they need to be lazyloaded before use.
|
||||
texturesPromise.then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false
|
||||
})
|
||||
this.invisible = false;
|
||||
});
|
||||
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"});
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {
|
||||
fontFamily: '"Press Start 2P"',
|
||||
fontSize: "8px",
|
||||
strokeThickness: 2,
|
||||
stroke: "gray",
|
||||
});
|
||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerName);
|
||||
|
||||
@ -71,18 +78,17 @@ export abstract class Character extends Container {
|
||||
useHandCursor: true,
|
||||
});
|
||||
|
||||
this.on('pointerover',() => {
|
||||
this.on("pointerover", () => {
|
||||
this.getOutlinePlugin()?.add(this.playerName, {
|
||||
thickness: 2,
|
||||
outlineColor: 0xffff00
|
||||
outlineColor: 0xffff00,
|
||||
});
|
||||
this.scene.markDirty();
|
||||
});
|
||||
this.on('pointerout',() => {
|
||||
this.on("pointerout", () => {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.scene.markDirty();
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
scene.add.existing(this);
|
||||
@ -97,17 +103,17 @@ export abstract class Character extends Container {
|
||||
|
||||
this.playAnimation(direction, moving);
|
||||
|
||||
if (typeof companion === 'string') {
|
||||
if (typeof companion === "string") {
|
||||
this.addCompanion(companion, companionTexturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
private getOutlinePlugin(): OutlinePipelinePlugin | undefined {
|
||||
return this.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined;
|
||||
return this.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||
}
|
||||
|
||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||
if (typeof texturePromise !== 'undefined') {
|
||||
if (typeof texturePromise !== "undefined") {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
}
|
||||
}
|
||||
@ -115,18 +121,18 @@ export abstract class Character extends Container {
|
||||
public addTextures(textures: string[], frame?: string | number): void {
|
||||
for (const texture of textures) {
|
||||
if (this.scene && !this.scene.textures.exists(texture)) {
|
||||
throw new TextureError('texture not found');
|
||||
throw new TextureError("texture not found");
|
||||
}
|
||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||
this.add(sprite);
|
||||
this.getPlayerAnimations(texture).forEach(d => {
|
||||
this.getPlayerAnimations(texture).forEach((d) => {
|
||||
this.scene.anims.create({
|
||||
key: d.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, { frames: d.frames }),
|
||||
frameRate: d.frameRate,
|
||||
repeat: d.repeat
|
||||
repeat: d.repeat,
|
||||
});
|
||||
});
|
||||
})
|
||||
// Needed, otherwise, animations are not handled correctly.
|
||||
if (this.scene) {
|
||||
this.scene.sys.updateList.add(sprite);
|
||||
@ -136,68 +142,77 @@ export abstract class Character extends Container {
|
||||
}
|
||||
|
||||
private getPlayerAnimations(name: string): AnimationData[] {
|
||||
return [{
|
||||
return [
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
},{
|
||||
repeat: -1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
repeat: 1,
|
||||
},
|
||||
{
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}];
|
||||
repeat: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
|
||||
if (this.invisible) return;
|
||||
for (const [texture, sprite] of this.sprites.entries()) {
|
||||
if (!sprite.anims) {
|
||||
console.error('ANIMS IS NOT DEFINED!!!');
|
||||
console.error("ANIMS IS NOT DEFINED!!!");
|
||||
return;
|
||||
}
|
||||
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
|
||||
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true);
|
||||
sprite.play(texture + "-" + direction + "-" + PlayerAnimationTypes.Walk, true);
|
||||
} else if (!moving) {
|
||||
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true);
|
||||
sprite.anims.play(texture + "-" + direction + "-" + PlayerAnimationTypes.Idle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -205,7 +220,7 @@ export abstract class Character extends Container {
|
||||
protected getBody(): Phaser.Physics.Arcade.Body {
|
||||
const body = this.body;
|
||||
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
|
||||
throw new Error('Container does not have arcade body');
|
||||
throw new Error("Container does not have arcade body");
|
||||
}
|
||||
return body;
|
||||
}
|
||||
@ -216,16 +231,20 @@ export abstract class Character extends Container {
|
||||
body.setVelocity(x, y);
|
||||
|
||||
// up or down animations are prioritized over left and right
|
||||
if (body.velocity.y < 0) { //moving up
|
||||
if (body.velocity.y < 0) {
|
||||
//moving up
|
||||
this.lastDirection = PlayerAnimationDirections.Up;
|
||||
this.playAnimation(PlayerAnimationDirections.Up, true);
|
||||
} else if (body.velocity.y > 0) { //moving down
|
||||
} else if (body.velocity.y > 0) {
|
||||
//moving down
|
||||
this.lastDirection = PlayerAnimationDirections.Down;
|
||||
this.playAnimation(PlayerAnimationDirections.Down, true);
|
||||
} else if (body.velocity.x > 0) { //moving right
|
||||
} else if (body.velocity.x > 0) {
|
||||
//moving right
|
||||
this.lastDirection = PlayerAnimationDirections.Right;
|
||||
this.playAnimation(PlayerAnimationDirections.Right, true);
|
||||
} else if (body.velocity.x < 0) { //moving left
|
||||
} else if (body.velocity.x < 0) {
|
||||
//moving left
|
||||
this.lastDirection = PlayerAnimationDirections.Left;
|
||||
this.playAnimation(PlayerAnimationDirections.Left, true);
|
||||
}
|
||||
@ -244,13 +263,13 @@ export abstract class Character extends Container {
|
||||
|
||||
say(text: string) {
|
||||
if (this.bubble) return;
|
||||
this.bubble = new SpeechBubble(this.scene, this, text)
|
||||
this.bubble = new SpeechBubble(this.scene, this, text);
|
||||
setTimeout(() => {
|
||||
if (this.bubble !== null) {
|
||||
this.bubble.destroy();
|
||||
this.bubble = null;
|
||||
}
|
||||
}, 3000)
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
@ -259,10 +278,17 @@ export abstract class Character extends Container {
|
||||
this.scene.sys.updateList.remove(sprite);
|
||||
}
|
||||
}
|
||||
this.list.forEach(objectContaining => objectContaining.destroy())
|
||||
this.list.forEach((objectContaining) => objectContaining.destroy());
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
isSilent() {
|
||||
isSilentStore.set(true);
|
||||
}
|
||||
noSilent() {
|
||||
isSilentStore.set(false);
|
||||
}
|
||||
|
||||
playEmote(emoteKey: string) {
|
||||
this.cancelPreviousEmote();
|
||||
|
||||
@ -287,11 +313,11 @@ export abstract class Character extends Container {
|
||||
alpha: 1,
|
||||
y: emoteY,
|
||||
},
|
||||
ease: 'Power2',
|
||||
ease: "Power2",
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
this.startPulseTransition(emoteY, scalingFactor);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -300,7 +326,7 @@ export abstract class Character extends Container {
|
||||
targets: this.emote,
|
||||
props: {
|
||||
y: emoteY * 1.3,
|
||||
scale: scalingFactor * 1.1
|
||||
scale: scalingFactor * 1.1,
|
||||
},
|
||||
duration: 250,
|
||||
yoyo: true,
|
||||
@ -308,7 +334,7 @@ export abstract class Character extends Container {
|
||||
completeDelay: 200,
|
||||
onComplete: () => {
|
||||
this.startExitTransition(emoteY);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -319,11 +345,11 @@ export abstract class Character extends Container {
|
||||
alpha: 0,
|
||||
y: 2 * emoteY,
|
||||
},
|
||||
ease: 'Power2',
|
||||
ease: "Power2",
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
this.destroyEmote();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -331,7 +357,7 @@ export abstract class Character extends Container {
|
||||
if (!this.emote) return;
|
||||
|
||||
this.emoteTween?.remove();
|
||||
this.destroyEmote()
|
||||
this.destroyEmote();
|
||||
}
|
||||
|
||||
private destroyEmote() {
|
||||
|
@ -1,333 +1,439 @@
|
||||
//The list of all the player textures, both the default models and the partial textures used for customization
|
||||
|
||||
export interface BodyResourceDescriptionListInterface {
|
||||
[key: string]: BodyResourceDescriptionInterface
|
||||
[key: string]: BodyResourceDescriptionInterface;
|
||||
}
|
||||
|
||||
export interface BodyResourceDescriptionInterface {
|
||||
name: string,
|
||||
img: string,
|
||||
level?: number
|
||||
name: 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"},
|
||||
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"},
|
||||
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" },
|
||||
};
|
||||
|
||||
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"}
|
||||
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 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"}
|
||||
|
||||
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 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"}
|
||||
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" },
|
||||
};
|
||||
|
||||
|
||||
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"}
|
||||
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" },
|
||||
};
|
||||
|
||||
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"}
|
||||
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" },
|
||||
};
|
||||
|
||||
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"}
|
||||
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" },
|
||||
};
|
||||
|
||||
export const LAYERS: BodyResourceDescriptionListInterface[] = [
|
||||
@ -336,9 +442,9 @@ export const LAYERS: BodyResourceDescriptionListInterface[] = [
|
||||
HAIR_RESOURCES,
|
||||
CLOTHES_RESOURCES,
|
||||
HATS_RESOURCES,
|
||||
ACCESSORIES_RESOURCES
|
||||
ACCESSORIES_RESOURCES,
|
||||
];
|
||||
|
||||
export const OBJECTS: BodyResourceDescriptionInterface[] = [
|
||||
{name:'teleportation', img:'resources/objects/teleportation.png'},
|
||||
{ name: "teleportation", img: "resources/objects/teleportation.png" },
|
||||
];
|
@ -6,9 +6,7 @@ export class SpeechBubble {
|
||||
private bubble: Phaser.GameObjects.Graphics;
|
||||
private content: Phaser.GameObjects.Text;
|
||||
|
||||
|
||||
constructor(scene: Scene, player: Character, text: string = "") {
|
||||
|
||||
const bubbleHeight = 50;
|
||||
const bubblePadding = 10;
|
||||
const bubbleWidth = bubblePadding * 2 + text.length * 10;
|
||||
@ -49,15 +47,24 @@ export class SpeechBubble {
|
||||
this.bubble.lineBetween(point2X, point2Y, point3X, point3Y);
|
||||
this.bubble.lineBetween(point1X, point1Y, point3X, point3Y);
|
||||
|
||||
this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: '20', color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } });
|
||||
this.content = scene.add.text(0, 0, text, {
|
||||
fontFamily: "Arial",
|
||||
fontSize: "20",
|
||||
color: "#000000",
|
||||
align: "center",
|
||||
wordWrap: { width: bubbleWidth - bubblePadding * 2 },
|
||||
});
|
||||
player.add(this.content);
|
||||
|
||||
const bounds = this.content.getBounds();
|
||||
this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2));
|
||||
this.content.setPosition(
|
||||
this.bubble.x + bubbleWidth / 2 - bounds.width / 2,
|
||||
this.bubble.y + bubbleHeight / 2 - bounds.height / 2
|
||||
);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.bubble.setVisible(false) //todo find a better way
|
||||
this.bubble.setVisible(false); //todo find a better way
|
||||
this.bubble.destroy();
|
||||
this.content.destroy();
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
export class Sprite extends Phaser.GameObjects.Sprite {
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: number | string) {
|
||||
super(scene, x, y, texture, frame);
|
||||
scene.sys.updateList.add(this);
|
||||
|
@ -5,19 +5,18 @@ import type {RadialMenuItem} from "../Components/RadialMenu";
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import type { Subscription } from "rxjs";
|
||||
|
||||
|
||||
interface RegisteredEmote extends BodyResourceDescriptionInterface {
|
||||
name: string;
|
||||
img: string;
|
||||
}
|
||||
|
||||
export const emotes: { [key: string]: RegisteredEmote } = {
|
||||
'emote-heart': {name: 'emote-heart', img: 'resources/emotes/heart-emote.png'},
|
||||
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/clap-emote.png'},
|
||||
'emote-hand': {name: 'emote-hand', img: 'resources/emotes/hand-emote.png'},
|
||||
'emote-thanks': {name: 'emote-thanks', img: 'resources/emotes/thanks-emote.png'},
|
||||
'emote-thumb-up': {name: 'emote-thumb-up', img: 'resources/emotes/thumb-up-emote.png'},
|
||||
'emote-thumb-down': {name: 'emote-thumb-down', img: 'resources/emotes/thumb-down-emote.png'},
|
||||
"emote-heart": { name: "emote-heart", img: "resources/emotes/heart-emote.png" },
|
||||
"emote-clap": { name: "emote-clap", img: "resources/emotes/clap-emote.png" },
|
||||
"emote-hand": { name: "emote-hand", img: "resources/emotes/hand-emote.png" },
|
||||
"emote-thanks": { name: "emote-thanks", img: "resources/emotes/thanks-emote.png" },
|
||||
"emote-thumb-up": { name: "emote-thumb-up", img: "resources/emotes/thumb-up-emote.png" },
|
||||
"emote-thumb-down": { name: "emote-thumb-down", img: "resources/emotes/thumb-down-emote.png" },
|
||||
};
|
||||
|
||||
export class EmoteManager {
|
||||
@ -27,11 +26,11 @@ export class EmoteManager {
|
||||
this.subscription = emoteEventStream.stream.subscribe((event) => {
|
||||
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
||||
if (actor) {
|
||||
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => {
|
||||
this.lazyLoadEmoteTexture(event.emoteName).then((emoteKey) => {
|
||||
actor.playEmote(emoteKey);
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
createLoadingPromise(loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) {
|
||||
return new Promise<string>((res) => {
|
||||
@ -39,18 +38,20 @@ export class EmoteManager {
|
||||
return res(playerResourceDescriptor.name);
|
||||
}
|
||||
loadPlugin.image(playerResourceDescriptor.name, playerResourceDescriptor.img);
|
||||
loadPlugin.once('filecomplete-image-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor.name));
|
||||
loadPlugin.once("filecomplete-image-" + playerResourceDescriptor.name, () =>
|
||||
res(playerResourceDescriptor.name)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
|
||||
const emoteDescriptor = emotes[textureKey];
|
||||
if (emoteDescriptor === undefined) {
|
||||
throw 'Emote not found!';
|
||||
throw "Emote not found!";
|
||||
}
|
||||
const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor);
|
||||
this.scene.load.start();
|
||||
return loadPromise
|
||||
return loadPromise;
|
||||
}
|
||||
|
||||
getMenuImages(): Promise<RadialMenuItem[]> {
|
||||
@ -60,7 +61,7 @@ export class EmoteManager {
|
||||
return {
|
||||
image: textureKey,
|
||||
name: textureKey,
|
||||
}
|
||||
};
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ export class GameManager {
|
||||
private characterLayers: string[] | null;
|
||||
private companion: string | null;
|
||||
private startRoom!: Room;
|
||||
private cameraSetup?: { video: unknown; audio: unknown };
|
||||
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;
|
||||
@ -26,6 +27,7 @@ export class GameManager {
|
||||
this.playerName = localUserStore.getName();
|
||||
this.characterLayers = localUserStore.getCharacterLayers();
|
||||
this.companion = localUserStore.getCompanion();
|
||||
this.cameraSetup = localUserStore.getCameraSetup();
|
||||
}
|
||||
|
||||
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
|
||||
@ -33,12 +35,17 @@ export class GameManager {
|
||||
this.startRoom = await connectionManager.initGameConnexion();
|
||||
this.loadMap(this.startRoom);
|
||||
|
||||
if (!this.playerName) {
|
||||
//If player name was not set show login scene with player name
|
||||
//If Room si not public and Auth was not set, show login scene to authenticate user (OpenID - SSO - Anonymous)
|
||||
if (!this.playerName || (this.startRoom.authenticationMandatory && !localUserStore.getAuthToken())) {
|
||||
return LoginSceneName;
|
||||
} else if (!this.characterLayers || !this.characterLayers.length) {
|
||||
return SelectCharacterSceneName;
|
||||
} else {
|
||||
} else if (this.cameraSetup == undefined) {
|
||||
return EnableCameraSceneName;
|
||||
} else {
|
||||
this.activeMenuSceneAndHelpCameraSettings();
|
||||
return this.startRoom.key;
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +91,14 @@ export class GameManager {
|
||||
public goToStartingMap(): void {
|
||||
console.log("starting " + (this.currentGameSceneName || this.startRoom.key));
|
||||
this.scenePlugin.start(this.currentGameSceneName || this.startRoom.key);
|
||||
this.activeMenuSceneAndHelpCameraSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return void
|
||||
*/
|
||||
private activeMenuSceneAndHelpCameraSettings(): void {
|
||||
if (
|
||||
!localUserStore.getHelpCameraSettingsShown() &&
|
||||
(!get(requestedMicrophoneState) || !get(requestedCameraState))
|
||||
@ -131,6 +145,10 @@ export class GameManager {
|
||||
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
||||
return this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
||||
}
|
||||
|
||||
public get currentStartedRoom() {
|
||||
return this.startRoom;
|
||||
}
|
||||
}
|
||||
|
||||
export const gameManager = new GameManager();
|
||||
|
55
front/src/Phaser/Game/GameMapPropertiesListener.ts
Normal file
55
front/src/Phaser/Game/GameMapPropertiesListener.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import type { GameScene } from "./GameScene";
|
||||
import type { GameMap } from "./GameMap";
|
||||
import { scriptUtils } from "../../Api/ScriptUtils";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||
import {
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
TRIGGER_WEBSITE_PROPERTIES,
|
||||
WEBSITE_MESSAGE_PROPERTIES,
|
||||
} from "../../WebRtc/LayoutManager";
|
||||
|
||||
export class GameMapPropertiesListener {
|
||||
constructor(private scene: GameScene, private gameMap: GameMap) {}
|
||||
|
||||
register() {
|
||||
this.gameMap.onPropertyChange("openTab", (newValue) => {
|
||||
if (typeof newValue == "string" && newValue.length) {
|
||||
scriptUtils.openTab(newValue);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("openWebsite");
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
} else {
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager.loadCoWebsite(
|
||||
newValue as string,
|
||||
this.scene.MapUrlFile,
|
||||
allProps.get("openWebsiteAllowApi") as boolean | undefined,
|
||||
allProps.get("openWebsitePolicy") as string | undefined,
|
||||
allProps.get("openWebsiteWidth") as number | undefined
|
||||
);
|
||||
layoutManagerActionStore.removeAction("openWebsite");
|
||||
};
|
||||
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
|
||||
if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to open web site";
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "openWebsite",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openWebsiteFunction(),
|
||||
userInputManager: this.scene.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openWebsiteFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { addLoader, removeLoader } from "../Components/Loader";
|
||||
import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
@ -92,6 +92,8 @@ import Tileset = Phaser.Tilemaps.Tileset;
|
||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||
import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
|
||||
import type { RadialMenuItem } from "../Components/RadialMenu";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
@ -290,8 +292,11 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
|
||||
//once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading.
|
||||
console.error("Error when loading: ", file);
|
||||
if (this.preloading) {
|
||||
//remove loader in progress
|
||||
removeLoader(this);
|
||||
|
||||
//display an error scene
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: "Network error",
|
||||
subTitle: "An error occurred while loading resource:",
|
||||
@ -580,6 +585,7 @@ export class GameScene extends DirtyScene {
|
||||
this.updateCameraOffset(box)
|
||||
);
|
||||
|
||||
new GameMapPropertiesListener(this, this.gameMap).register();
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
|
||||
if (!this.room.isDisconnected()) {
|
||||
@ -713,25 +719,8 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
// When connection is performed, let's connect SimplePeer
|
||||
this.simplePeer = new SimplePeer(this.connection);
|
||||
peerStore.connectToSimplePeer(this.simplePeer);
|
||||
screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
|
||||
videoFocusStore.connectToSimplePeer(this.simplePeer);
|
||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||
|
||||
const self = this;
|
||||
this.simplePeer.registerPeerConnectionListener({
|
||||
onConnect(peer) {
|
||||
//self.openChatIcon.setVisible(true);
|
||||
audioManagerVolumeStore.setTalking(true);
|
||||
},
|
||||
onDisconnect(userId: number) {
|
||||
if (self.simplePeer.getNbConnections() === 0) {
|
||||
//self.openChatIcon.setVisible(false);
|
||||
audioManagerVolumeStore.setTalking(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
//listen event to share position of user
|
||||
this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this));
|
||||
this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this));
|
||||
@ -825,40 +814,7 @@ export class GameScene extends DirtyScene {
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("openWebsite");
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
} else {
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager.loadCoWebsite(
|
||||
newValue as string,
|
||||
this.MapUrlFile,
|
||||
allProps.get("openWebsiteAllowApi") as boolean | undefined,
|
||||
allProps.get("openWebsitePolicy") as string | undefined,
|
||||
allProps.get("openWebsiteWidth") as number | undefined
|
||||
);
|
||||
layoutManagerActionStore.removeAction("openWebsite");
|
||||
};
|
||||
|
||||
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
|
||||
if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to open web site";
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "openWebsite",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openWebsiteFunction(),
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openWebsiteFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
@ -898,8 +854,10 @@ export class GameScene extends DirtyScene {
|
||||
this.gameMap.onPropertyChange("silent", (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === "") {
|
||||
this.connection?.setSilent(false);
|
||||
this.CurrentPlayer.noSilent();
|
||||
} else {
|
||||
this.connection?.setSilent(true);
|
||||
this.CurrentPlayer.isSilent();
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange("playAudio", (newValue, oldValue, allProps) => {
|
||||
@ -1306,7 +1264,9 @@ ${escapedMessage}
|
||||
if (!targetRoom.isEqual(this.room)) {
|
||||
if (this.scene.get(targetRoom.key) === null) {
|
||||
console.error("next room not loaded", targetRoom.key);
|
||||
return;
|
||||
// Try to load next dame room from exit URL
|
||||
// The policy of room can to be updated during a session and not load before
|
||||
await this.loadNextGameFromExitUrl(targetRoom.key);
|
||||
}
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop();
|
||||
|
@ -1,10 +1,7 @@
|
||||
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import type { Subscription } from "rxjs";
|
||||
import type { GameMap } from "./GameMap";
|
||||
import type { ITile, ITiledMapObject } from "../Map/ITiledMap";
|
||||
import type { Var } from "svelte/types/compiler/interfaces";
|
||||
import { init } from "svelte/internal";
|
||||
import type { ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer } from "../Map/ITiledMap";
|
||||
|
||||
interface Variable {
|
||||
defaultValue: unknown;
|
||||
@ -100,6 +97,12 @@ export class SharedVariablesManager {
|
||||
private static findVariablesInMap(gameMap: GameMap): Map<string, Variable> {
|
||||
const objects = new Map<string, Variable>();
|
||||
for (const layer of gameMap.getMap().layers) {
|
||||
this.recursiveFindVariablesInLayer(layer, objects);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private static recursiveFindVariablesInLayer(layer: ITiledMapLayer, objects: Map<string, Variable>): void {
|
||||
if (layer.type === "objectgroup") {
|
||||
for (const object of layer.objects) {
|
||||
if (object.type === "variable") {
|
||||
@ -107,15 +110,18 @@ export class SharedVariablesManager {
|
||||
console.warn(
|
||||
'Warning, a variable object is using a Tiled "template". WorkAdventure does not support objects generated from Tiled templates.'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We store a copy of the object (to make it immutable)
|
||||
objects.set(object.name, this.iTiledObjectToVariable(object));
|
||||
}
|
||||
}
|
||||
} else if (layer.type === "group") {
|
||||
for (const innerLayer of layer.layers) {
|
||||
this.recursiveFindVariablesInLayer(innerLayer, objects);
|
||||
}
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private static iTiledObjectToVariable(object: ITiledMapObject): Variable {
|
||||
|
@ -11,13 +11,12 @@ class SoundManager {
|
||||
return soundPromise;
|
||||
}
|
||||
soundPromise = new Promise<BaseSound>((res) => {
|
||||
|
||||
const sound = soundManager.get(soundUrl);
|
||||
if (sound !== null) {
|
||||
return res(sound);
|
||||
}
|
||||
loadPlugin.audio(soundUrl, soundUrl);
|
||||
loadPlugin.once('filecomplete-audio-' + soundUrl, () => {
|
||||
loadPlugin.once("filecomplete-audio-" + soundUrl, () => {
|
||||
res(soundManager.add(soundUrl));
|
||||
});
|
||||
loadPlugin.start();
|
||||
@ -26,7 +25,12 @@ class SoundManager {
|
||||
return soundPromise;
|
||||
}
|
||||
|
||||
public async playSound(loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string, config: SoundConfig|undefined) : Promise<void> {
|
||||
public async playSound(
|
||||
loadPlugin: LoaderPlugin,
|
||||
soundManager: BaseSoundManager,
|
||||
soundUrl: string,
|
||||
config: SoundConfig | undefined
|
||||
): Promise<void> {
|
||||
const sound = await this.loadSound(loadPlugin, soundManager, soundUrl);
|
||||
if (config === undefined) sound.play();
|
||||
else sound.play(config);
|
||||
|
@ -13,7 +13,13 @@ export class ActionableItem {
|
||||
private isSelectable: boolean = false;
|
||||
private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>();
|
||||
|
||||
public constructor(private id: number, private sprite: Sprite, private eventHandler: GameScene, private activationRadius: number, private onActivateCallback: (item: ActionableItem) => void) {
|
||||
public constructor(
|
||||
private id: number,
|
||||
private sprite: Sprite,
|
||||
private eventHandler: GameScene,
|
||||
private activationRadius: number,
|
||||
private onActivateCallback: (item: ActionableItem) => void
|
||||
) {
|
||||
this.activationRadiusSquared = activationRadius * activationRadius;
|
||||
}
|
||||
|
||||
@ -45,7 +51,7 @@ export class ActionableItem {
|
||||
|
||||
this.getOutlinePlugin()?.add(this.sprite, {
|
||||
thickness: 2,
|
||||
outlineColor: 0xffff00
|
||||
outlineColor: 0xffff00,
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,7 +67,7 @@ export class ActionableItem {
|
||||
}
|
||||
|
||||
private getOutlinePlugin(): OutlinePipelinePlugin | undefined {
|
||||
return this.sprite.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined;
|
||||
return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as Phaser from 'phaser';
|
||||
import * as Phaser from "phaser";
|
||||
import { Scene } from "phaser";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import type { ITiledMapObject } from "../../Map/ITiledMap";
|
||||
@ -7,80 +7,85 @@ import type {GameScene} from "../../Game/GameScene";
|
||||
import { ActionableItem } from "../ActionableItem";
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
const isComputerState =
|
||||
new tg.IsInterface().withProperties({
|
||||
const isComputerState = new tg.IsInterface()
|
||||
.withProperties({
|
||||
status: tg.isString,
|
||||
}).get();
|
||||
})
|
||||
.get();
|
||||
type ComputerState = tg.GuardedType<typeof isComputerState>;
|
||||
|
||||
let state: ComputerState = {
|
||||
'status': 'off'
|
||||
status: "off",
|
||||
};
|
||||
|
||||
export default {
|
||||
preload: (loader: Phaser.Loader.LoaderPlugin): void => {
|
||||
loader.atlas('computer', '/resources/items/computer/computer.png', '/resources/items/computer/computer_atlas.json');
|
||||
loader.atlas(
|
||||
"computer",
|
||||
"/resources/items/computer/computer.png",
|
||||
"/resources/items/computer/computer_atlas.json"
|
||||
);
|
||||
},
|
||||
create: (scene: GameScene): void => {
|
||||
scene.anims.create({
|
||||
key: 'computer_off',
|
||||
key: "computer_off",
|
||||
frames: [
|
||||
{
|
||||
key: 'computer',
|
||||
frame: 'computer_off'
|
||||
}
|
||||
key: "computer",
|
||||
frame: "computer_off",
|
||||
},
|
||||
],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
});
|
||||
scene.anims.create({
|
||||
key: 'computer_run',
|
||||
key: "computer_run",
|
||||
frames: [
|
||||
{
|
||||
key: 'computer',
|
||||
frame: 'computer_on1'
|
||||
key: "computer",
|
||||
frame: "computer_on1",
|
||||
},
|
||||
{
|
||||
key: 'computer',
|
||||
frame: 'computer_on2'
|
||||
}
|
||||
key: "computer",
|
||||
frame: "computer_on2",
|
||||
},
|
||||
],
|
||||
frameRate: 5,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
});
|
||||
},
|
||||
factory: (scene: GameScene, object: ITiledMapObject, initState: unknown): ActionableItem => {
|
||||
if (initState !== undefined) {
|
||||
if (!isComputerState(initState)) {
|
||||
throw new Error('Invalid state received for computer object');
|
||||
throw new Error("Invalid state received for computer object");
|
||||
}
|
||||
state = initState;
|
||||
}
|
||||
|
||||
// Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/
|
||||
const computer = new Sprite(scene, object.x, object.y, 'computer');
|
||||
const computer = new Sprite(scene, object.x, object.y, "computer");
|
||||
scene.add.existing(computer);
|
||||
if (state.status === 'on') {
|
||||
computer.anims.play('computer_run');
|
||||
if (state.status === "on") {
|
||||
computer.anims.play("computer_run");
|
||||
}
|
||||
|
||||
const item = new ActionableItem(object.id, computer, scene, 32, (item: ActionableItem) => {
|
||||
if (state.status === 'off') {
|
||||
state.status = 'on';
|
||||
item.emit('TURN_ON', state);
|
||||
if (state.status === "off") {
|
||||
state.status = "on";
|
||||
item.emit("TURN_ON", state);
|
||||
} else {
|
||||
state.status = 'off';
|
||||
item.emit('TURN_OFF', state);
|
||||
state.status = "off";
|
||||
item.emit("TURN_OFF", state);
|
||||
}
|
||||
});
|
||||
item.on('TURN_ON', () => {
|
||||
computer.anims.play('computer_run');
|
||||
item.on("TURN_ON", () => {
|
||||
computer.anims.play("computer_run");
|
||||
});
|
||||
item.on('TURN_OFF', () => {
|
||||
computer.anims.play('computer_off');
|
||||
item.on("TURN_OFF", () => {
|
||||
computer.anims.play("computer_off");
|
||||
});
|
||||
|
||||
return item;
|
||||
//scene.add.sprite(object.x, object.y, 'computer');
|
||||
}
|
||||
},
|
||||
} as ItemFactoryInterface;
|
||||
|
@ -5,7 +5,6 @@ import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
@ -17,7 +16,7 @@ export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
@ -31,7 +30,7 @@ export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private getTextures(): CharacterTexture[] | undefined {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
|
||||
export const EnableCameraSceneName = "EnableCameraScene";
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { loginSceneVisibleIframeStore, loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
|
||||
export const LoginSceneName = "LoginScene";
|
||||
|
||||
@ -18,6 +20,16 @@ export class LoginScene extends ResizableScene {
|
||||
preload() {}
|
||||
|
||||
create() {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
//If authentication is mandatory, push authentication iframe
|
||||
if (
|
||||
localUserStore.getAuthToken() == undefined &&
|
||||
gameManager.currentStartedRoom &&
|
||||
gameManager.currentStartedRoom?.authenticationMandatory
|
||||
) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
loginSceneVisibleIframeStore.set(true);
|
||||
}
|
||||
loginSceneVisibleStore.set(true);
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,10 @@ export abstract class ResizableScene extends Scene {
|
||||
* @param defaultWidth The width of the DOM element. We try to compute it but it may not be available if called from "create".
|
||||
*/
|
||||
public centerXDomElement(object: DOMElement, defaultWidth: number): void {
|
||||
object.x = (this.scale.width / 2) -
|
||||
(
|
||||
object
|
||||
&& object.node
|
||||
&& object.node.getBoundingClientRect().width > 0
|
||||
? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom)
|
||||
: (defaultWidth / this.scale.zoom)
|
||||
);
|
||||
object.x =
|
||||
this.scale.width / 2 -
|
||||
(object && object.node && object.node.getBoundingClientRect().width > 0
|
||||
? object.node.getBoundingClientRect().width / 2 / this.scale.zoom
|
||||
: defaultWidth / this.scale.zoom);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SelectCharacterScene } from "./SelectCharacterScene";
|
||||
|
||||
export class SelectCharacterMobileScene extends SelectCharacterScene {
|
||||
|
||||
create() {
|
||||
super.create();
|
||||
this.onResize();
|
||||
@ -19,45 +18,41 @@ export class SelectCharacterMobileScene extends SelectCharacterScene {
|
||||
if (this.currentSelectUser !== num) {
|
||||
playerVisible = false;
|
||||
}
|
||||
if( num === (this.currentSelectUser + 1) ){
|
||||
if (num === this.currentSelectUser + 1) {
|
||||
playerY -= deltaY;
|
||||
playerX += deltaX;
|
||||
playerScale = 0.8;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( num === (this.currentSelectUser + 2) ){
|
||||
if (num === this.currentSelectUser + 2) {
|
||||
playerY -= deltaY;
|
||||
playerX += (deltaX * 2);
|
||||
playerX += deltaX * 2;
|
||||
playerScale = 0.8;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( num === (this.currentSelectUser - 1) ){
|
||||
if (num === this.currentSelectUser - 1) {
|
||||
playerY -= deltaY;
|
||||
playerX -= deltaX;
|
||||
playerScale = 0.8;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( num === (this.currentSelectUser - 2) ){
|
||||
if (num === this.currentSelectUser - 2) {
|
||||
playerY -= deltaY;
|
||||
playerX -= (deltaX * 2);
|
||||
playerX -= deltaX * 2;
|
||||
playerScale = 0.8;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
return {playerX, playerY, playerScale, playerOpacity, playerVisible}
|
||||
return { playerX, playerY, playerScale, playerOpacity, playerVisible };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pixel position by on column and row number
|
||||
*/
|
||||
protected getCharacterPosition(): [number, number] {
|
||||
return [
|
||||
this.game.renderer.width / 2,
|
||||
this.game.renderer.height / 3
|
||||
];
|
||||
return [this.game.renderer.width / 2, this.game.renderer.height / 3];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
|
@ -1,13 +1,10 @@
|
||||
|
||||
export enum PlayerAnimationDirections {
|
||||
Down = 'down',
|
||||
Left = 'left',
|
||||
Up = 'up',
|
||||
Right = 'right',
|
||||
Down = "down",
|
||||
Left = "left",
|
||||
Up = "up",
|
||||
Right = "right",
|
||||
}
|
||||
export enum PlayerAnimationTypes {
|
||||
Walk = 'walk',
|
||||
Idle = 'idle',
|
||||
Walk = "walk",
|
||||
Idle = "idle",
|
||||
}
|
||||
|
||||
|
||||
|
@ -37,7 +37,7 @@ export class Player extends Character {
|
||||
this.emoteMenu.y = this.y;
|
||||
}
|
||||
};
|
||||
this.scene.events.addListener('postupdate', this.updateListener);
|
||||
this.scene.events.addListener("postupdate", this.updateListener);
|
||||
}
|
||||
|
||||
moveUser(delta: number): void {
|
||||
@ -104,7 +104,7 @@ export class Player extends Character {
|
||||
|
||||
openEmoteMenu(emotes: RadialMenuItem[]): void {
|
||||
this.cancelPreviousEmote();
|
||||
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)
|
||||
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes);
|
||||
this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => {
|
||||
this.closeEmoteMenu();
|
||||
this.emit(requestEmoteEventName, item.name);
|
||||
@ -112,6 +112,13 @@ export class Player extends Character {
|
||||
});
|
||||
}
|
||||
|
||||
isSilent() {
|
||||
super.isSilent();
|
||||
}
|
||||
noSilent() {
|
||||
super.noSilent();
|
||||
}
|
||||
|
||||
closeEmoteMenu(): void {
|
||||
if (!this.emoteMenu) return;
|
||||
this.emoteMenu.destroy();
|
||||
@ -119,7 +126,7 @@ export class Player extends Character {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.scene.events.removeListener('postupdate', this.updateListener);
|
||||
this.scene.events.removeListener("postupdate", this.updateListener);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ export class WAError extends Error {
|
||||
private _details: string;
|
||||
|
||||
constructor(title: string, subTitle: string, details: string) {
|
||||
super(title+' - '+subTitle+' - '+details);
|
||||
super(title + " - " + subTitle + " - " + details);
|
||||
this._title = title;
|
||||
this._subTitle = subTitle;
|
||||
this._details = details;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
@ -23,14 +22,14 @@ export class HdpiManager {
|
||||
*
|
||||
* @param realPixelScreenSize
|
||||
*/
|
||||
public getOptimalGameSize(realPixelScreenSize: Size): { game: Size, real: Size } {
|
||||
public getOptimalGameSize(realPixelScreenSize: Size): { game: Size; real: Size } {
|
||||
const realPixelNumber = realPixelScreenSize.width * realPixelScreenSize.height;
|
||||
// If the screen has not a definition small enough to match the minimum number of pixels we want to display,
|
||||
// let's make the canvas the size of the screen (in real pixels)
|
||||
if (realPixelNumber <= this.minRecommendedGamePixelsNumber) {
|
||||
return {
|
||||
game: realPixelScreenSize,
|
||||
real: realPixelScreenSize
|
||||
real: realPixelScreenSize,
|
||||
};
|
||||
}
|
||||
|
||||
@ -49,8 +48,8 @@ export class HdpiManager {
|
||||
real: {
|
||||
width: realPixelScreenSize.width,
|
||||
height: realPixelScreenSize.height,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier);
|
||||
@ -58,8 +57,12 @@ export class HdpiManager {
|
||||
|
||||
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
|
||||
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
|
||||
const minGameHeight = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.height / realPixelScreenSize.width);
|
||||
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
|
||||
const minGameHeight = Math.sqrt(
|
||||
(this.absoluteMinPixelNumber * realPixelScreenSize.height) / realPixelScreenSize.width
|
||||
);
|
||||
const minGameWidth = Math.sqrt(
|
||||
(this.absoluteMinPixelNumber * realPixelScreenSize.width) / realPixelScreenSize.height
|
||||
);
|
||||
|
||||
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
||||
this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel;
|
||||
@ -72,9 +75,8 @@ export class HdpiManager {
|
||||
real: {
|
||||
width: realPixelScreenSize.width,
|
||||
height: realPixelScreenSize.height,
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@ -85,8 +87,8 @@ export class HdpiManager {
|
||||
real: {
|
||||
width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel,
|
||||
height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +97,7 @@ export class HdpiManager {
|
||||
private getOptimalZoomLevel(realPixelNumber: number): number {
|
||||
const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
|
||||
if (1.5 <= result && result < 2) {
|
||||
return 1.5
|
||||
return 1.5;
|
||||
} else {
|
||||
return Math.floor(result);
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import type {Game} from "../Game/Game";
|
||||
import { ResizableScene } from "../Login/ResizableScene";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
|
||||
|
||||
class WaScaleManager {
|
||||
private hdpiManager: HdpiManager;
|
||||
private scaleManager!: ScaleManager;
|
||||
@ -30,19 +29,22 @@ class WaScaleManager {
|
||||
devicePixelRatio = window.devicePixelRatio;
|
||||
}
|
||||
|
||||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
|
||||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({
|
||||
width: width * devicePixelRatio,
|
||||
height: height * devicePixelRatio,
|
||||
});
|
||||
|
||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio)
|
||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||
|
||||
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
|
||||
const style = this.scaleManager.canvas.style;
|
||||
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
|
||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
|
||||
style.width = Math.ceil(realSize.width / devicePixelRatio) + "px";
|
||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + "px";
|
||||
|
||||
// Resize the game element at the same size at the canvas
|
||||
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game').style;
|
||||
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>("game").style;
|
||||
gameStyle.width = style.width;
|
||||
gameStyle.height = style.height;
|
||||
|
||||
@ -81,7 +83,6 @@ class WaScaleManager {
|
||||
public get uiScalingFactor(): number {
|
||||
return this.actualZoom > 1 ? 1 : 1.2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const waScaleManager = new WaScaleManager(640 * 480, 196 * 196);
|
||||
|
@ -15,18 +15,18 @@ export class PinchManager {
|
||||
// We are smoothing its value with previous values to prevent the flicking.
|
||||
let smoothPinch = 1;
|
||||
|
||||
this.pinch.on('pinchstart', () => {
|
||||
this.pinch.on("pinchstart", () => {
|
||||
smoothPinch = 1;
|
||||
});
|
||||
|
||||
|
||||
this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line
|
||||
// eslint-disable-next-line
|
||||
this.pinch.on("pinch", (pinch: any) => {
|
||||
if (pinch.scaleFactor > 1.2 || pinch.scaleFactor < 0.8) {
|
||||
// Pinch too fast! Probably a bad measure.
|
||||
return;
|
||||
}
|
||||
|
||||
smoothPinch = 3/5*smoothPinch + 2/5*pinch.scaleFactor;
|
||||
smoothPinch = (3 / 5) * smoothPinch + (2 / 5) * pinch.scaleFactor;
|
||||
if (this.scene instanceof GameScene) {
|
||||
this.scene.zoomByFactor(smoothPinch);
|
||||
} else {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { get, writable } from "svelte/store";
|
||||
import { peerStore } from "./PeerStore";
|
||||
|
||||
export interface audioManagerVolume {
|
||||
muted: boolean;
|
||||
@ -103,3 +104,7 @@ export const audioManagerVisibilityStore = writable(false);
|
||||
export const audioManagerVolumeStore = createAudioManagerVolumeStore();
|
||||
|
||||
export const audioManagerFileStore = createAudioManagerFileStore();
|
||||
|
||||
peerStore.subscribe((peers) => {
|
||||
audioManagerVolumeStore.setTalking(peers.size > 0);
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ function createErrorStore() {
|
||||
},
|
||||
clearMessages: (): void => {
|
||||
set([]);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
export class BrowserTooOldError extends Error {
|
||||
static NAME = 'BrowserTooOldError';
|
||||
static NAME = "BrowserTooOldError";
|
||||
|
||||
constructor() {
|
||||
super('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.');
|
||||
super(
|
||||
"Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome."
|
||||
);
|
||||
this.name = BrowserTooOldError.NAME;
|
||||
}
|
||||
}
|
||||
|
10
front/src/Stores/Errors/MediaStreamConstraintsError.ts
Normal file
10
front/src/Stores/Errors/MediaStreamConstraintsError.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export class MediaStreamConstraintsError extends Error {
|
||||
static NAME = "MediaStreamConstraintsError";
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
"Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome."
|
||||
);
|
||||
this.name = MediaStreamConstraintsError.NAME;
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
export class WebviewOnOldIOS extends Error {
|
||||
static NAME = 'WebviewOnOldIOS';
|
||||
static NAME = "WebviewOnOldIOS";
|
||||
|
||||
constructor() {
|
||||
super('Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above.');
|
||||
super(
|
||||
"Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above."
|
||||
);
|
||||
this.name = WebviewOnOldIOS.NAME;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const loginSceneVisibleStore = writable(false);
|
||||
export const loginSceneVisibleIframeStore = writable(false);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { derived, get, Readable, readable, writable, Writable } from "svelte/store";
|
||||
import { derived, get, Readable, readable, writable } from "svelte/store";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
import { userMovingStore } from "./GameStore";
|
||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||
@ -9,6 +9,7 @@ import { WebviewOnOldIOS } from "./Errors/WebviewOnOldIOS";
|
||||
import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility";
|
||||
import { peerStore } from "./PeerStore";
|
||||
import { privacyShutdownStore } from "./PrivacyShutdownStore";
|
||||
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
|
||||
|
||||
/**
|
||||
* A store that contains the camera state requested by the user (on or off).
|
||||
@ -255,6 +256,12 @@ export const mediaStreamConstraintsStore = derived(
|
||||
video: currentVideoConstraint,
|
||||
audio: currentAudioConstraint,
|
||||
});
|
||||
localUserStore.setCameraSetup(
|
||||
JSON.stringify({
|
||||
video: currentVideoConstraint,
|
||||
audio: currentAudioConstraint,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -413,7 +420,7 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
if (constraints.video !== false) {
|
||||
if (constraints.video !== false || constraints.audio !== false) {
|
||||
console.info(
|
||||
"Error. Unable to get microphone and/or camera access. Trying audio only.",
|
||||
$mediaStreamConstraintsStore,
|
||||
@ -425,7 +432,17 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
||||
error: e,
|
||||
});
|
||||
// Let's try without video constraints
|
||||
if (constraints.video !== false) {
|
||||
requestedCameraState.disableWebcam();
|
||||
}
|
||||
if (constraints.audio !== false) {
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
}
|
||||
} else if (!constraints.video && !constraints.audio) {
|
||||
set({
|
||||
type: "error",
|
||||
error: new MediaStreamConstraintsError(),
|
||||
});
|
||||
} else {
|
||||
console.info(
|
||||
"Error. Unable to get microphone and/or camera access.",
|
||||
@ -561,3 +578,8 @@ localStreamStore.subscribe((streamResult) => {
|
||||
* A store containing the real active media is mobile
|
||||
*/
|
||||
export const obtainedMediaConstraintIsMobileStore = writable(false);
|
||||
|
||||
/**
|
||||
* A store containing if user is silent, so if he is in silent zone. This permit to show et hide camera of user
|
||||
*/
|
||||
export const isSilentStore = writable(false);
|
||||
|
@ -6,6 +6,8 @@ import { CONTACT_URL } from "../Enum/EnvironmentVariable";
|
||||
export const menuIconVisiblilityStore = writable(false);
|
||||
export const menuVisiblilityStore = writable(false);
|
||||
export const menuInputFocusStore = writable(false);
|
||||
export const loginUrlStore = writable(false);
|
||||
export const userIsConnected = writable(false);
|
||||
|
||||
let warningContainerTimeout: Timeout | null = null;
|
||||
function createWarningContainerStore() {
|
||||
|
@ -1,37 +1,29 @@
|
||||
import { readable, writable } from "svelte/store";
|
||||
import type { RemotePeer, SimplePeer } from "../WebRtc/SimplePeer";
|
||||
import { VideoPeer } from "../WebRtc/VideoPeer";
|
||||
import { ScreenSharingPeer } from "../WebRtc/ScreenSharingPeer";
|
||||
import type { VideoPeer } from "../WebRtc/VideoPeer";
|
||||
import type { ScreenSharingPeer } from "../WebRtc/ScreenSharingPeer";
|
||||
|
||||
/**
|
||||
* A store that contains the list of (video) peers we are connected to.
|
||||
*/
|
||||
function createPeerStore() {
|
||||
let peers = new Map<number, VideoPeer>();
|
||||
|
||||
const { subscribe, set, update } = writable(peers);
|
||||
const { subscribe, set, update } = writable(new Map<number, VideoPeer>());
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
connectToSimplePeer: (simplePeer: SimplePeer) => {
|
||||
peers = new Map<number, VideoPeer>();
|
||||
set(peers);
|
||||
simplePeer.registerPeerConnectionListener({
|
||||
onConnect(peer: RemotePeer) {
|
||||
if (peer instanceof VideoPeer) {
|
||||
pushNewPeer(peer: VideoPeer) {
|
||||
update((users) => {
|
||||
users.set(peer.userId, peer);
|
||||
return users;
|
||||
});
|
||||
}
|
||||
},
|
||||
onDisconnect(userId: number) {
|
||||
removePeer(userId: number) {
|
||||
update((users) => {
|
||||
users.delete(userId);
|
||||
return users;
|
||||
});
|
||||
},
|
||||
});
|
||||
cleanupStore() {
|
||||
set(new Map<number, VideoPeer>());
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -40,31 +32,24 @@ function createPeerStore() {
|
||||
* A store that contains the list of screen sharing peers we are connected to.
|
||||
*/
|
||||
function createScreenSharingPeerStore() {
|
||||
let peers = new Map<number, ScreenSharingPeer>();
|
||||
|
||||
const { subscribe, set, update } = writable(peers);
|
||||
const { subscribe, set, update } = writable(new Map<number, ScreenSharingPeer>());
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
connectToSimplePeer: (simplePeer: SimplePeer) => {
|
||||
peers = new Map<number, ScreenSharingPeer>();
|
||||
set(peers);
|
||||
simplePeer.registerPeerConnectionListener({
|
||||
onConnect(peer: RemotePeer) {
|
||||
if (peer instanceof ScreenSharingPeer) {
|
||||
pushNewPeer(peer: ScreenSharingPeer) {
|
||||
update((users) => {
|
||||
users.set(peer.userId, peer);
|
||||
return users;
|
||||
});
|
||||
}
|
||||
},
|
||||
onDisconnect(userId: number) {
|
||||
removePeer(userId: number) {
|
||||
update((users) => {
|
||||
users.delete(userId);
|
||||
return users;
|
||||
});
|
||||
},
|
||||
});
|
||||
cleanupStore() {
|
||||
set(new Map<number, ScreenSharingPeer>());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { writable } from "svelte/store";
|
||||
import type { PlayerInterface } from "../Phaser/Game/PlayerInterface";
|
||||
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||
import { getRandomColor } from "../WebRtc/ColorGenerator";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
|
||||
let idCount = 0;
|
||||
|
||||
|
@ -175,6 +175,7 @@ export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set)
|
||||
export interface ScreenSharingLocalMedia {
|
||||
uniqueId: string;
|
||||
stream: MediaStream | null;
|
||||
userId?: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,9 +13,7 @@ function createSoundPlayingStore() {
|
||||
},
|
||||
soundEnded: () => {
|
||||
set(null);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { writable } from "svelte/store";
|
||||
import type { RemotePeer, SimplePeer } from "../WebRtc/SimplePeer";
|
||||
import { VideoPeer } from "../WebRtc/VideoPeer";
|
||||
import { ScreenSharingPeer } from "../WebRtc/ScreenSharingPeer";
|
||||
import { get, writable } from "svelte/store";
|
||||
import type { Streamable } from "./StreamableCollectionStore";
|
||||
import { peerStore } from "./PeerStore";
|
||||
|
||||
/**
|
||||
* A store that contains the peer / media that has currently the "importance" focus.
|
||||
*/
|
||||
function createVideoFocusStore() {
|
||||
const { subscribe, set, update } = writable<Streamable | null>(null);
|
||||
const { subscribe, set } = writable<Streamable | null>(null);
|
||||
|
||||
let focusedMedia: Streamable | null = null;
|
||||
|
||||
@ -23,27 +21,17 @@ function createVideoFocusStore() {
|
||||
set(null);
|
||||
},
|
||||
toggleFocus: (media: Streamable) => {
|
||||
if (media !== focusedMedia) {
|
||||
focusedMedia = media;
|
||||
} else {
|
||||
focusedMedia = null;
|
||||
}
|
||||
focusedMedia = media !== focusedMedia ? media : null;
|
||||
set(focusedMedia);
|
||||
},
|
||||
connectToSimplePeer: (simplePeer: SimplePeer) => {
|
||||
simplePeer.registerPeerConnectionListener({
|
||||
onConnect(peer: RemotePeer) {},
|
||||
onDisconnect(userId: number) {
|
||||
if (
|
||||
(focusedMedia instanceof VideoPeer || focusedMedia instanceof ScreenSharingPeer) &&
|
||||
focusedMedia.userId === userId
|
||||
) {
|
||||
set(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const videoFocusStore = createVideoFocusStore();
|
||||
|
||||
peerStore.subscribe((peers) => {
|
||||
const focusedMedia: Streamable | null = get(videoFocusStore);
|
||||
if (focusedMedia && focusedMedia.userId !== undefined && !peers.get(focusedMedia.userId)) {
|
||||
videoFocusStore.removeFocus();
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,4 @@
|
||||
|
||||
class TouchScreenManager {
|
||||
|
||||
readonly supportTouchScreen: boolean;
|
||||
|
||||
constructor() {
|
||||
@ -9,7 +7,7 @@ class TouchScreenManager {
|
||||
|
||||
//found here: https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript#4819886
|
||||
detectTouchscreen(): boolean {
|
||||
return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
|
||||
return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Room } from "../Connexion/Room";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
|
||||
export enum GameConnexionTypes {
|
||||
anonymous = 1,
|
||||
@ -7,13 +8,16 @@ export enum GameConnexionTypes {
|
||||
empty,
|
||||
unknown,
|
||||
jwt,
|
||||
login,
|
||||
}
|
||||
|
||||
//this class is responsible with analysing and editing the game's url
|
||||
class UrlManager {
|
||||
public getGameConnexionType(): GameConnexionTypes {
|
||||
const url = window.location.pathname.toString();
|
||||
if (url === "/jwt") {
|
||||
if (url === "/login") {
|
||||
return GameConnexionTypes.login;
|
||||
} else if (url === "/jwt") {
|
||||
return GameConnexionTypes.jwt;
|
||||
} else if (url.includes("_/")) {
|
||||
return GameConnexionTypes.anonymous;
|
||||
@ -35,6 +39,8 @@ class UrlManager {
|
||||
|
||||
public pushRoomIdToUrl(room: Room): void {
|
||||
if (window.location.pathname === room.id) return;
|
||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||
localUserStore.setLastRoomUrl(room.key);
|
||||
const hash = window.location.hash;
|
||||
const search = room.search.toString();
|
||||
history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash);
|
||||
|
@ -166,7 +166,13 @@ class CoWebsiteManager {
|
||||
return iframe;
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string, widthPercent?: number): void {
|
||||
public loadCoWebsite(
|
||||
url: string,
|
||||
base: string,
|
||||
allowApi?: boolean,
|
||||
allowPolicy?: string,
|
||||
widthPercent?: number
|
||||
): void {
|
||||
this.load();
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
|
||||
|
@ -1,12 +1,9 @@
|
||||
export function isIOS(): boolean {
|
||||
return [
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod'
|
||||
].includes(navigator.platform)
|
||||
return (
|
||||
["iPad Simulator", "iPhone Simulator", "iPod Simulator", "iPad", "iPhone", "iPod"].includes(
|
||||
navigator.platform
|
||||
) ||
|
||||
// iPad on iOS 13 detection
|
||||
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
||||
(navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
||||
);
|
||||
}
|
||||
|
@ -1,23 +1,47 @@
|
||||
import { JITSI_URL } from "../Enum/EnvironmentVariable";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import { coWebsiteManager } from "./CoWebsiteManager";
|
||||
import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
|
||||
import { get } from "svelte/store";
|
||||
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
interface jitsiConfigInterface {
|
||||
startWithAudioMuted: boolean
|
||||
startWithVideoMuted: boolean
|
||||
prejoinPageEnabled: boolean
|
||||
startWithAudioMuted: boolean;
|
||||
startWithVideoMuted: boolean;
|
||||
prejoinPageEnabled: boolean;
|
||||
}
|
||||
|
||||
interface JitsiOptions {
|
||||
jwt?: string;
|
||||
roomName: string;
|
||||
width: string;
|
||||
height: string;
|
||||
parentNode: HTMLElement;
|
||||
configOverwrite: jitsiConfigInterface;
|
||||
interfaceConfigOverwrite: typeof defaultInterfaceConfig;
|
||||
onload?: Function;
|
||||
}
|
||||
|
||||
interface JitsiApi {
|
||||
executeCommand: (command: string, ...args: Array<unknown>) => void;
|
||||
|
||||
addListener: (type: string, callback: Function) => void;
|
||||
removeListener: (type: string, callback: Function) => void;
|
||||
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
JitsiMeetExternalAPI: new (domain: string, options: JitsiOptions) => JitsiApi;
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultConfig = (): jitsiConfigInterface => {
|
||||
return {
|
||||
startWithAudioMuted: !get(requestedMicrophoneState),
|
||||
startWithVideoMuted: !get(requestedCameraState),
|
||||
prejoinPageEnabled: false
|
||||
}
|
||||
}
|
||||
prejoinPageEnabled: false,
|
||||
};
|
||||
};
|
||||
|
||||
const mergeConfig = (config?: object) => {
|
||||
const currentDefaultConfig = getDefaultConfig();
|
||||
@ -27,11 +51,17 @@ const mergeConfig = (config?: object) => {
|
||||
return {
|
||||
...currentDefaultConfig,
|
||||
...config,
|
||||
startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted ? true : currentDefaultConfig.startWithAudioMuted,
|
||||
startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted ? true : currentDefaultConfig.startWithVideoMuted,
|
||||
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled ? true : currentDefaultConfig.prejoinPageEnabled
|
||||
}
|
||||
}
|
||||
startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted
|
||||
? true
|
||||
: currentDefaultConfig.startWithAudioMuted,
|
||||
startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted
|
||||
? true
|
||||
: currentDefaultConfig.startWithVideoMuted,
|
||||
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled
|
||||
? true
|
||||
: currentDefaultConfig.prejoinPageEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
const defaultInterfaceConfig = {
|
||||
SHOW_CHROME_EXTENSION_BANNER: false,
|
||||
@ -49,28 +79,48 @@ const defaultInterfaceConfig = {
|
||||
SHOW_WATERMARK_FOR_GUESTS: false,
|
||||
|
||||
TOOLBAR_BUTTONS: [
|
||||
'microphone', 'camera', 'closedcaptions', 'desktop', /*'embedmeeting',*/ 'fullscreen',
|
||||
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', /*'invite',*/ 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', /*'security'*/
|
||||
"microphone",
|
||||
"camera",
|
||||
"closedcaptions",
|
||||
"desktop",
|
||||
/*'embedmeeting',*/ "fullscreen",
|
||||
"fodeviceselection",
|
||||
"hangup",
|
||||
"profile",
|
||||
"chat",
|
||||
"recording",
|
||||
"livestreaming",
|
||||
"etherpad",
|
||||
"sharedvideo",
|
||||
"settings",
|
||||
"raisehand",
|
||||
"videoquality",
|
||||
"filmstrip",
|
||||
/*'invite',*/ "feedback",
|
||||
"stats",
|
||||
"shortcuts",
|
||||
"tileview",
|
||||
"videobackgroundblur",
|
||||
"download",
|
||||
"help",
|
||||
"mute-everyone" /*'security'*/,
|
||||
],
|
||||
};
|
||||
|
||||
const slugify = (...args: (string | number)[]): string => {
|
||||
const value = args.join(' ')
|
||||
const value = args.join(" ");
|
||||
|
||||
return value
|
||||
.normalize('NFD') // split an accented letter in the base letter and the accent
|
||||
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
|
||||
.normalize("NFD") // split an accented letter in the base letter and the accent
|
||||
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced)
|
||||
.replace(/\s+/g, '-') // separator
|
||||
}
|
||||
.replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
|
||||
.replace(/\s+/g, "-"); // separator
|
||||
};
|
||||
|
||||
class JitsiFactory {
|
||||
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
private jitsiApi?: JitsiApi;
|
||||
private audioCallback = this.onAudioChange.bind(this);
|
||||
private videoCallback = this.onVideoChange.bind(this);
|
||||
private jitsiScriptLoaded: boolean = false;
|
||||
@ -79,11 +129,19 @@ class JitsiFactory {
|
||||
* Slugifies the room name and prepends the room name with the instance
|
||||
*/
|
||||
public getRoomName(roomName: string, instance: string): string {
|
||||
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||
return slugify(instance.replace("/", "-") + "-" + roomName);
|
||||
}
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string, jitsiWidth?: number): void {
|
||||
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
||||
public start(
|
||||
roomName: string,
|
||||
playerName: string,
|
||||
jwt?: string,
|
||||
config?: object,
|
||||
interfaceConfig?: object,
|
||||
jitsiUrl?: string,
|
||||
jitsiWidth?: number
|
||||
): void {
|
||||
coWebsiteManager.insertCoWebsite(async (cowebsiteDiv) => {
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
@ -94,18 +152,18 @@ class JitsiFactory {
|
||||
|
||||
const domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error('Missing JITSI_URL environment variable or jitsiUrl parameter in the map.')
|
||||
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
|
||||
}
|
||||
await this.loadJitsiScript(domain);
|
||||
|
||||
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
const options: JitsiOptions = {
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: cowebsiteDiv,
|
||||
configOverwrite: mergeConfig(config),
|
||||
interfaceConfigOverwrite: {...defaultInterfaceConfig, ...interfaceConfig}
|
||||
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
|
||||
};
|
||||
if (!options.jwt) {
|
||||
delete options.jwt;
|
||||
@ -115,12 +173,12 @@ class JitsiFactory {
|
||||
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
|
||||
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
|
||||
this.jitsiApi.executeCommand('displayName', playerName);
|
||||
this.jitsiApi.executeCommand("displayName", playerName);
|
||||
|
||||
this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback);
|
||||
this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback);
|
||||
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
|
||||
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
|
||||
});
|
||||
}), jitsiWidth);
|
||||
}, jitsiWidth);
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
@ -128,8 +186,8 @@ class JitsiFactory {
|
||||
return;
|
||||
}
|
||||
await coWebsiteManager.closeCoWebsite();
|
||||
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
||||
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
||||
this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback);
|
||||
this.jitsiApi.removeListener("videoMuteStatusChanged", this.videoCallback);
|
||||
this.jitsiApi?.dispose();
|
||||
}
|
||||
|
||||
@ -159,20 +217,17 @@ class JitsiFactory {
|
||||
this.jitsiScriptLoaded = true;
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + domain + '/external_api.js';
|
||||
const jitsiScript = document.createElement("script");
|
||||
jitsiScript.src = "https://" + domain + "/external_api.js";
|
||||
jitsiScript.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
jitsiScript.onerror = () => {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
|
||||
document.head.appendChild(jitsiScript);
|
||||
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility
|
||||
import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||
import { get } from "svelte/store";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
import { MediaStreamConstraintsError } from "../Stores/Errors/MediaStreamConstraintsError";
|
||||
|
||||
export class MediaManager {
|
||||
startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||
@ -24,7 +25,7 @@ export class MediaManager {
|
||||
constructor() {
|
||||
localStreamStore.subscribe((result) => {
|
||||
if (result.type === "error") {
|
||||
console.error(result.error);
|
||||
if (result.error.name !== MediaStreamConstraintsError.NAME) {
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "cameraAccessDenied",
|
||||
type: "warning",
|
||||
@ -34,6 +35,7 @@ export class MediaManager {
|
||||
},
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
}
|
||||
//remove it after 10 sec
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("cameraAccessDenied");
|
||||
|
@ -10,6 +10,7 @@ import { blackListManager } from "./BlackListManager";
|
||||
import { get } from "svelte/store";
|
||||
import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore";
|
||||
import { playersStore } from "../Stores/PlayersStore";
|
||||
import { peerStore, screenSharingPeerStore } from "../Stores/PeerStore";
|
||||
|
||||
export interface UserSimplePeerInterface {
|
||||
userId: number;
|
||||
@ -20,12 +21,6 @@ export interface UserSimplePeerInterface {
|
||||
|
||||
export type RemotePeer = VideoPeer | ScreenSharingPeer;
|
||||
|
||||
export interface PeerConnectionListener {
|
||||
onConnect(user: RemotePeer): void;
|
||||
|
||||
onDisconnect(userId: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class manages connections to all the peers in the same group as me.
|
||||
*/
|
||||
@ -37,12 +32,14 @@ export class SimplePeer {
|
||||
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
|
||||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||
private readonly unsubscribers: (() => void)[] = [];
|
||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||
private readonly userId: number;
|
||||
private lastWebrtcUserName: string | undefined;
|
||||
private lastWebrtcPassword: string | undefined;
|
||||
|
||||
constructor(private Connection: RoomConnection) {
|
||||
//we make sure we don't get any old peer.
|
||||
peerStore.cleanupStore();
|
||||
screenSharingPeerStore.cleanupStore();
|
||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
|
||||
this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this);
|
||||
@ -73,14 +70,6 @@ export class SimplePeer {
|
||||
this.initialise();
|
||||
}
|
||||
|
||||
public registerPeerConnectionListener(peerConnectionListener: PeerConnectionListener) {
|
||||
this.peerConnectionListeners.push(peerConnectionListener);
|
||||
}
|
||||
|
||||
public getNbConnections(): number {
|
||||
return this.Users.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* permit to listen when user could start visio
|
||||
*/
|
||||
@ -164,9 +153,7 @@ export class SimplePeer {
|
||||
}
|
||||
this.PeerConnectionArray.set(user.userId, peer);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onConnect(peer);
|
||||
}
|
||||
peerStore.pushNewPeer(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
@ -214,9 +201,7 @@ export class SimplePeer {
|
||||
);
|
||||
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onConnect(peer);
|
||||
}
|
||||
screenSharingPeerStore.pushNewPeer(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
@ -255,12 +240,11 @@ export class SimplePeer {
|
||||
for (const userId of this.PeerScreenSharingConnectionArray.keys()) {
|
||||
this.closeScreenSharingConnection(userId);
|
||||
this.PeerScreenSharingConnectionArray.delete(userId);
|
||||
screenSharingPeerStore.removePeer(userId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
peerStore.removePeer(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,6 +286,8 @@ export class SimplePeer {
|
||||
for (const unsubscriber of this.unsubscribers) {
|
||||
unsubscriber();
|
||||
}
|
||||
peerStore.cleanupStore();
|
||||
screenSharingPeerStore.cleanupStore();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -325,7 +311,6 @@ export class SimplePeer {
|
||||
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
||||
const uuid = playersStore.getPlayerById(data.userId)?.userUuid || "";
|
||||
if (blackListManager.isBlackListed(uuid)) return;
|
||||
console.log("receiveWebrtcScreenSharingSignal", data);
|
||||
const streamResult = get(screenSharingLocalStreamStore);
|
||||
let stream: MediaStream | null = null;
|
||||
if (streamResult.type === "success" && streamResult.stream !== null) {
|
||||
|
@ -4,7 +4,7 @@ import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||
import { blackListManager } from "./BlackListManager";
|
||||
import type { Subscription } from "rxjs";
|
||||
import type { UserSimplePeerInterface } from "./SimplePeer";
|
||||
import { get, readable, Readable, Unsubscriber } from "svelte/store";
|
||||
import { readable, Readable, Unsubscriber } from "svelte/store";
|
||||
import {
|
||||
localStreamStore,
|
||||
obtainedMediaConstraintIsMobileStore,
|
||||
@ -12,7 +12,7 @@ import {
|
||||
ObtainedMediaStreamConstraints,
|
||||
} from "../Stores/MediaStore";
|
||||
import { playersStore } from "../Stores/PlayersStore";
|
||||
import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore";
|
||||
import { chatMessagesStore, newChatMessageStore } from "../Stores/ChatStore";
|
||||
import { getIceServersConfig } from "../Components/Video/utils";
|
||||
import { isMobile } from "../Enum/EnvironmentVariable";
|
||||
|
||||
|
10
front/src/rex-plugins.d.ts
vendored
10
front/src/rex-plugins.d.ts
vendored
@ -1,16 +1,16 @@
|
||||
declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' {
|
||||
declare module "phaser3-rex-plugins/plugins/virtualjoystick.js" {
|
||||
const content: any; // eslint-disable-line
|
||||
export default content;
|
||||
}
|
||||
declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' {
|
||||
declare module "phaser3-rex-plugins/plugins/gestures-plugin.js" {
|
||||
const content: any; // eslint-disable-line
|
||||
export default content;
|
||||
}
|
||||
declare module 'phaser3-rex-plugins/plugins/webfontloader-plugin.js' {
|
||||
declare module "phaser3-rex-plugins/plugins/webfontloader-plugin.js" {
|
||||
const content: any; // eslint-disable-line
|
||||
export default content;
|
||||
}
|
||||
declare module 'phaser3-rex-plugins/plugins/outlinepipeline-plugin.js' {
|
||||
declare module "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js" {
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
|
||||
class OutlinePipelinePlugin {
|
||||
@ -21,6 +21,6 @@ declare module 'phaser3-rex-plugins/plugins/outlinepipeline-plugin.js' {
|
||||
|
||||
export default OutlinePipelinePlugin;
|
||||
}
|
||||
declare module 'phaser3-rex-plugins/plugins/gestures.js' {
|
||||
declare module "phaser3-rex-plugins/plugins/gestures.js" {
|
||||
export const Pinch: any; // eslint-disable-line
|
||||
}
|
||||
|
@ -399,62 +399,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.audioplayer:first-child {
|
||||
display: grid;
|
||||
grid: 2rem / 4rem 10rem;
|
||||
}
|
||||
|
||||
.audioplayer > button, .audioplayer > div, .audioplayer > label {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.audioplayer > div {
|
||||
padding-right: 1.2rem;
|
||||
}
|
||||
|
||||
#audioplayerctrl {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: calc(50% - 120px);
|
||||
padding: 0.3rem 0.5rem;
|
||||
color: white;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
#audioplayer_mute {
|
||||
max-width: 5rem;
|
||||
border: none;
|
||||
}
|
||||
#audioplayer_mute:focus, #audioplayer_mute:active {
|
||||
outline: none;
|
||||
}
|
||||
#audioplayer_mute > svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
#audioplayer_volume_icon_playing.muted {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
#audioplayerctrl > #audioplayer_volume {
|
||||
width: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/*
|
||||
* sollte eigentlich in den aspect-ratio teil ..
|
||||
*/
|
||||
#audioplayerctrl.loading {
|
||||
transform: translateY(-90%);
|
||||
}
|
||||
#audioplayerctrl.hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
/*
|
||||
* Style Input Range
|
||||
* https://www.cssportal.com/style-input-range/
|
||||
@ -1139,3 +1083,19 @@ div.action.danger p.action-body{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.is-silent {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
border-radius: 15px 15px 15px 15px;
|
||||
max-height: 20%;
|
||||
transition: right 350ms;
|
||||
right: -20vw;
|
||||
background-color: black;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
padding: 30px 20px;
|
||||
}
|
||||
div.is-silent.hide {
|
||||
right: 15px;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
||||
import { PROFILE_URL } from "./src/Enum/EnvironmentVariable";
|
||||
|
||||
const mode = process.env.NODE_ENV ?? "development";
|
||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||
@ -191,6 +192,7 @@ module.exports = {
|
||||
UPLOADER_URL: null,
|
||||
ADMIN_URL: undefined,
|
||||
CONTACT_URL: null,
|
||||
PROFILE_URL: null,
|
||||
DEBUG_MODE: null,
|
||||
STUN_SERVER: null,
|
||||
TURN_SERVER: null,
|
||||
|
@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
|
||||
@ -17,6 +17,7 @@ export class AuthenticateController extends BaseController {
|
||||
this.openIDCallback();
|
||||
this.register();
|
||||
this.anonymLogin();
|
||||
this.profileCallback();
|
||||
}
|
||||
|
||||
openIDLogin() {
|
||||
@ -48,14 +49,31 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { code, nonce } = parse(req.getQuery());
|
||||
const { code, nonce, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: token }));
|
||||
} catch (err) {
|
||||
console.info("User was not connected", err);
|
||||
}
|
||||
}
|
||||
|
||||
//user have not token created, check data on hydra and create token
|
||||
const userInfo = await openIDClient.getUserInfo(code as string, nonce as string);
|
||||
const email = userInfo.email || userInfo.sub;
|
||||
if (!email) {
|
||||
throw new Error("No email in the response");
|
||||
}
|
||||
const authToken = jwtTokenManager.createAuthToken(email);
|
||||
const authToken = jwtTokenManager.createAuthToken(email, userInfo.access_token);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken }));
|
||||
@ -63,6 +81,30 @@ export class AuthenticateController extends BaseController {
|
||||
return this.errorToResponse(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/logout-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
|
||||
const { token } = parse(req.getQuery());
|
||||
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be logout on Hydra");
|
||||
}
|
||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
||||
} catch (error) {
|
||||
console.error("openIDCallback => logout-callback", error);
|
||||
} finally {
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Try to login with an admin token
|
||||
@ -136,4 +178,39 @@ export class AuthenticateController extends BaseController {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
profileCallback() {
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/profile-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { userIdentify, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
|
||||
//get login profile
|
||||
res.writeStatus("302");
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
} catch (error) {
|
||||
return this.errorToResponse(error, res);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorToResponse(error, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||
const API_URL = process.env.API_URL || "";
|
||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "";
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
@ -19,6 +20,7 @@ export {
|
||||
SECRET_KEY,
|
||||
API_URL,
|
||||
ADMIN_API_URL,
|
||||
ADMIN_URL,
|
||||
ADMIN_API_TOKEN,
|
||||
ALLOW_ARTILLERY,
|
||||
CPU_OVERHEAT_THRESHOLD,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
@ -141,6 +141,15 @@ class AdminApi {
|
||||
return data.data;
|
||||
});
|
||||
}
|
||||
|
||||
/*TODO add constant to use profile companny*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!ADMIN_URL) {
|
||||
throw new Error("No admin backoffice set!");
|
||||
}
|
||||
|
||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
||||
|
@ -6,13 +6,13 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||
hydraAccessToken?: string;
|
||||
}
|
||||
export const tokenInvalidException = "tokenInvalid";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createAuthToken(identifier: string) {
|
||||
//TODO fix me 200d when ory authentication will be available
|
||||
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "200d" });
|
||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||
}
|
||||
|
||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Issuer, Client } from "openid-client";
|
||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
@ -31,12 +31,31 @@ class OpenIDClient {
|
||||
});
|
||||
}
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string }> {
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.userinfo(tokenSet);
|
||||
return client.userinfo(tokenSet).then((res) => {
|
||||
return {
|
||||
...res,
|
||||
email: res.email as string,
|
||||
sub: res.sub,
|
||||
access_token: tokenSet.access_token as string,
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public logoutUser(token: string): Promise<void> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.revoke(token);
|
||||
});
|
||||
}
|
||||
|
||||
public checkTokenAuth(token: string): Promise<IntrospectionResponse> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.userinfo(token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user