Merge branch 'develop' of ssh://git.bstly.de:222/_Bastler/partey_workadventure

This commit is contained in:
_Bastler
2022-02-20 13:28:29 +01:00
52 changed files with 848 additions and 221 deletions
+30 -25
View File
@@ -4,6 +4,7 @@ WORKDIR /usr/src
COPY messages .
RUN yarn install && yarn ts-proto
# webpack build
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
WORKDIR /usr/src
@@ -16,6 +17,15 @@ COPY --from=messages /usr/src/ts-proto-generated/protos src/Messages/ts-proto-ge
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts
COPY --from=messages /usr/src/JsonMessages src/Messages/JsonMessages
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
&& apt-get install -y \
gettext-base
RUN rm dist/iframe.html
RUN yarn run typesafe-i18n
RUN yarn run build
RUN ./templater.sh
# passing arguments as environment
ARG DEBUG_MODE
ARG JITSI_URL
@@ -35,33 +45,28 @@ ARG DISABLE_NOTIFICATIONS
ARG SKIP_RENDER_OPTIMIZATIONS
ARG OPID_LOGIN_SCREEN_PROVIDER
# Removing the iframe.html file from the final image as this adds a XSS attack.
# iframe.html is only in dev mode to circumvent a limitation
RUN rm dist/iframe.html
ENV NODE_ENV=production
RUN ./templater.sh
RUN yarn run typesafe-i18n
RUN \
DEBUG_MODE=$DEBUG_MODE \
JITSI_URL=$JITSI_URL \
JITSI_PRIVATE_MODE=$JITSI_PRIVATE_MODE \
PUSHER_URL=$PUSHER_URL \
ICON_URL=$ICON_URL \
ADMIN_URL=$ADMIN_URL \
STUN_SERVER=$STUN_SERVER \
TURN_SERVER=$TURN_SERVER \
TURN_USER=$TURN_USER \
TURN_PASSWORD=$TURN_PASSWORD \
MAX_PER_GROUP=$MAX_PER_GROUP \
MAX_USERNAME_LENGTH=$MAX_USERNAME_LENGTH \
PROFILE_URL=$PROFILE_URL \
START_ROOM_URL=$START_ROOM_URL \
DISABLE_NOTIFICATIONS=$DISABLE_NOTIFICATIONS \
SKIP_RENDER_OPTIMIZATIONS=$SKIP_RENDER_OPTIMIZATIONS \
OPID_LOGIN_SCREEN_PROVIDER=$OPID_LOGIN_SCREEN_PROVIDER \
yarn run build
ENV DEBUG_MODE=$DEBUG_MODE
ENV JITSI_URL=$JITSI_URL
ENV JITSI_PRIVATE_MODE=$JITSI_PRIVATE_MODE
ENV PUSHER_URL=$PUSHER_URL
ENV ICON_URL=$ICON_URL
ENV ADMIN_URL=$ADMIN_URL
ENV STUN_SERVER=$STUN_SERVER
ENV TURN_SERVER=$TURN_SERVER
ENV TURN_USER=$TURN_USER
ENV TURN_PASSWORD=$TURN_PASSWORD
ENV MAX_PER_GROUP=$MAX_PER_GROUP
ENV MAX_USERNAME_LENGTH=$MAX_USERNAME_LENGTH
ENV PROFILE_URL=$PROFILE_URL
ENV START_ROOM_URL=$START_ROOM_URL
ENV DISABLE_NOTIFICATIONS=$DISABLE_NOTIFICATIONS
ENV SKIP_RENDER_OPTIMIZATIONS=$SKIP_RENDER_OPTIMIZATIONS
ENV OPID_LOGIN_SCREEN_PROVIDER=$OPID_LOGIN_SCREEN_PROVIDER
RUN envsubst < dist/env-config.template.js > dist/env-config.js
# final production image
FROM nginx:mainline-alpine
COPY front/nginx-vhost.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /usr/src/dist /usr/share/nginx/html
RUN cat /usr/share/nginx/html/env-config.js
+1
View File
@@ -4,3 +4,4 @@ index.html
style.*.css
!env-config.template.js
*.png
!/resources/**
+1 -2
View File
@@ -29,9 +29,8 @@
<script src="/env-config.js"></script>
<base href="/">
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
<title>WorkAdventure</title>
<title>Partey</title>
</head>
<body id="body" style="margin: 0; background-color: #000">
<div class="main-container" id="main-container">
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+1
View File
@@ -42,6 +42,7 @@
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"@16bits/nes.css": "^2.3.2",
"@fontsource/press-start-2p": "^4.3.0",
"@joeattardi/emoji-button": "^4.6.2",
"@types/simple-peer": "^9.11.1",
@@ -9,6 +9,7 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface()
position: tg.isOptional(tg.isNumber),
closable: tg.isOptional(tg.isBoolean),
lazy: tg.isOptional(tg.isBoolean),
hint: tg.isOptional(tg.isString),
})
.get();
+3 -1
View File
@@ -48,7 +48,8 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
widthPercent?: number,
position?: number,
closable?: boolean,
lazy?: boolean
lazy?: boolean,
hint?: string
): Promise<CoWebsite> {
const result = await queryWorkadventure({
type: "openCoWebsite",
@@ -60,6 +61,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
position,
closable,
lazy,
hint,
},
});
return new CoWebsite(result.id);
@@ -3,7 +3,7 @@
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
import MediaBox from "../Video/MediaBox.svelte";
export let highlightedEmbedScreen: EmbedScreen | null;
export let highlightedEmbedScreen: EmbedScreen | undefined;
export let full = false;
$: clickable = !full;
</script>
@@ -8,6 +8,8 @@
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import { i18nJson } from "../../i18n/locales";
export let index: number;
export let coWebsite: CoWebsite;
@@ -65,6 +67,17 @@
return false;
}
function i18n(text: string | undefined): string {
if (typeof text === "string") {
return i18nJson(text);
}
return "";
}
function sanitize(html : string | undefined): string {
return HtmlUtils.sanitize(html);
}
let isHighlight: boolean = false;
let isMain: boolean = false;
$: {
@@ -73,9 +86,9 @@
$mainCoWebsite !== undefined &&
$mainCoWebsite.getId() === coWebsite.getId();
isHighlight =
$highlightedEmbedScreen !== null &&
$highlightedEmbedScreen.type === "cowebsite" &&
$highlightedEmbedScreen.embed.getId() === coWebsite.getId();
$highlightedEmbedScreen !== undefined &&
$highlightedEmbedScreen?.type === "cowebsite" &&
$highlightedEmbedScreen?.embed.getId() === coWebsite.getId();
}
</script>
@@ -188,6 +201,12 @@
/>
</rect>
</svg>
{#if coWebsite.getHint() && !isMain && !isHighlight }
<div class="cowebsite-thumbnail-hint nes-balloon from-left">
<p>{@html sanitize(i18n(coWebsite.getHint()))}</p>
</div>
{/if}
</div>
<style lang="scss">
@@ -320,5 +339,19 @@
padding: 7px;
}
}
.cowebsite-thumbnail-hint {
display: none;
position:absolute;
padding: 8px 4px;
text-align: center;
bottom: 50px;
width: clamp(150px, 15vw, 15vw);
left: -10px;
}
&:hover .cowebsite-thumbnail-hint {
display: inline-block;
}
}
</style>
@@ -22,8 +22,6 @@
position: absolute;
bottom: 5px;
left: 2%;
overflow-x: auto;
overflow-y: hidden;
&.vertical {
height: auto !important;
@@ -5,6 +5,7 @@
audioConstraintStore,
cameraListStore,
localStreamStore,
localVolumeStore,
microphoneListStore,
videoConstraintStore,
} from "../../Stores/MediaStore";
@@ -38,7 +39,7 @@
let stream: MediaStream | null;
const unsubscribe = localStreamStore.subscribe((value) => {
const unsubscribeLocalStreamStore = localStreamStore.subscribe((value) => {
if (value.type === "success") {
stream = value.stream;
@@ -59,7 +60,9 @@
}
});
onDestroy(unsubscribe);
onDestroy(() => {
unsubscribeLocalStreamStore();
});
function normalizeDeviceName(label: string): string {
// remove IDs (that can appear in Chrome, like: "HD Pro Webcam (4df7:4eda)"
@@ -86,7 +89,7 @@
<img class="background-img" src={cinemaCloseImg} alt="" />
</div>
{/if}
<HorizontalSoundMeterWidget {stream} />
<HorizontalSoundMeterWidget volume={$localVolumeStore} />
<section class="selectWebcamForm">
{#if $cameraListStore.length > 1}
@@ -1,50 +1,8 @@
<script lang="typescript">
import { AudioContext } from "standardized-audio-context";
import { SoundMeter } from "../../Phaser/Components/SoundMeter";
import { onDestroy } from "svelte";
export let stream: MediaStream | null;
let volume = 0;
export let volume = 0;
const NB_BARS = 20;
let timeout: ReturnType<typeof setTimeout>;
const soundMeter = new SoundMeter();
let display = false;
let error = false;
$: {
if (stream && stream.getAudioTracks().length > 0) {
display = true;
soundMeter.connectToSource(stream, new AudioContext());
if (timeout) {
clearInterval(timeout);
error = false;
}
timeout = setInterval(() => {
try {
volume = parseInt(((soundMeter.getVolume() / 100) * NB_BARS).toFixed(0));
} catch (err) {
if (!error) {
console.error(err);
error = true;
}
}
}, 100);
} else {
display = false;
}
}
onDestroy(() => {
soundMeter.stop();
if (timeout) {
clearInterval(timeout);
}
});
function color(i: number, volume: number) {
const red = (255 * i) / NB_BARS;
const green = 255 * (1 - i / NB_BARS);
@@ -58,7 +16,7 @@
}
</script>
<div class="horizontal-sound-meter" class:active={display}>
<div class="horizontal-sound-meter" class:active={volume !== undefined}>
{#each [...Array(NB_BARS).keys()] as i (i)}
<div style={color(i, volume)} />
{/each}
+6 -4
View File
@@ -1,5 +1,5 @@
<script lang="typescript">
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
import { localVolumeStore, obtainedMediaConstraintStore } from "../Stores/MediaStore";
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
import SoundMeterWidget from "./SoundMeterWidget.svelte";
import { onDestroy, onMount } from "svelte";
@@ -8,7 +8,7 @@
let stream: MediaStream | null;
const unsubscribe = localStreamStore.subscribe((value) => {
const unsubscribeLocalStreamStore = localStreamStore.subscribe((value) => {
if (value.type === "success") {
stream = value.stream;
} else {
@@ -16,7 +16,9 @@
}
});
onDestroy(unsubscribe);
onDestroy(() => {
unsubscribeLocalStreamStore();
});
let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
@@ -51,7 +53,7 @@
<div class="is-silent">{$LL.camera.my.silentZone()}</div>
{:else if $localStreamStore.type === "success" && $localStreamStore.stream}
<video class="my-cam-video" use:srcObject={stream} autoplay muted playsinline />
<SoundMeterWidget {stream} />
<SoundMeterWidget volume={$localVolumeStore} />
{/if}
</div>
+2 -43
View File
@@ -1,47 +1,6 @@
<script lang="typescript">
import { AudioContext } from "standardized-audio-context";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { onDestroy } from "svelte";
export let stream: MediaStream | null;
let volume = 0;
let timeout: ReturnType<typeof setTimeout>;
const soundMeter = new SoundMeter();
let display = false;
let error = false;
$: {
if (stream && stream.getAudioTracks().length > 0) {
display = true;
soundMeter.connectToSource(stream, new AudioContext());
if (timeout) {
clearInterval(timeout);
error = false;
}
timeout = setInterval(() => {
try {
volume = soundMeter.getVolume();
} catch (err) {
if (!error) {
console.error(err);
error = true;
}
}
}, 100);
} else {
display = false;
}
}
onDestroy(() => {
soundMeter.stop();
if (timeout) {
clearInterval(timeout);
}
});
export let volume = 0;
let display = true;
</script>
<div class="sound-progress" class:active={display}>
@@ -18,6 +18,7 @@
export let peer: VideoPeer;
let streamStore = peer.streamStore;
let volumeStore = peer.volumeStore;
let name = peer.userName;
let statusStore = peer.statusStore;
let constraintStore = peer.constraintsStore;
@@ -93,7 +94,7 @@
/>
<img src={blockSignImg} draggable="false" on:dragstart|preventDefault={noDrag} class="block-logo" alt="Block" />
{#if $constraintStore && $constraintStore.audio !== false}
<SoundMeterWidget stream={$streamStore} />
<SoundMeterWidget volume={$volumeStore} />
{/if}
</div>
+2 -3
View File
@@ -378,9 +378,8 @@ class ConnectionManager {
} catch (err) {
console.warn("Could not set locale", err);
}
} else {
console.log("no locale", locale);
}
}
//user connected, set connected store for menu at true
userIsConnected.set(true);
}
+28 -29
View File
@@ -1,4 +1,4 @@
import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
import { AudioContext, IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
/**
* Class to measure the sound volume of a media stream
@@ -6,41 +6,15 @@ import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "
export class SoundMeter {
private instant: number;
private clip: number;
//private script: ScriptProcessorNode;
private analyser: IAnalyserNode<IAudioContext> | undefined;
private dataArray: Uint8Array | undefined;
private context: IAudioContext | undefined;
private source: IMediaStreamAudioSourceNode<IAudioContext> | undefined;
constructor() {
constructor(mediaStream: MediaStream) {
this.instant = 0.0;
this.clip = 0.0;
//this.script = context.createScriptProcessor(2048, 1, 1);
}
private init(context: IAudioContext) {
this.context = context;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength);
}
public connectToSource(stream: MediaStream, context: IAudioContext): void {
if (this.source !== undefined) {
this.stop();
}
this.init(context);
this.source = this.context?.createMediaStreamSource(stream);
if (this.analyser !== undefined) {
this.source?.connect(this.analyser);
}
//analyser.connect(distortion);
//distortion.connect(this.context.destination);
//this.analyser.connect(this.context.destination);
this.connectToSource(mediaStream, new AudioContext());
}
public getVolume(): number {
@@ -78,4 +52,29 @@ export class SoundMeter {
this.dataArray = undefined;
this.source = undefined;
}
private init(context: IAudioContext) {
this.context = context;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength);
}
private connectToSource(stream: MediaStream, context: IAudioContext): void {
if (this.source !== undefined) {
this.stop();
}
this.init(context);
this.source = this.context?.createMediaStreamSource(stream);
if (this.analyser !== undefined) {
this.source?.connect(this.analyser);
}
//analyser.connect(distortion);
//distortion.connect(this.context.destination);
//this.analyser.connect(this.context.destination);
}
}
+58
View File
@@ -0,0 +1,58 @@
import { Easing } from "../../types";
export class TalkIcon extends Phaser.GameObjects.Image {
private shown: boolean;
private showAnimationTween?: Phaser.Tweens.Tween;
private defaultPosition: { x: number; y: number };
private defaultScale: number;
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, "iconTalk");
this.defaultPosition = { x, y };
this.defaultScale = 0.3;
this.shown = false;
this.setAlpha(0);
this.setScale(this.defaultScale);
this.scene.add.existing(this);
}
public show(show: boolean = true, forceClose: boolean = false): void {
if (this.shown === show && !forceClose) {
return;
}
this.showAnimation(show, forceClose);
}
private showAnimation(show: boolean = true, forceClose: boolean = false) {
if (forceClose && !show) {
this.showAnimationTween?.stop();
} else if (this.showAnimationTween?.isPlaying()) {
return;
}
this.shown = show;
if (show) {
this.y += 50;
this.scale = 0.05;
this.alpha = 0;
}
this.showAnimationTween = this.scene.tweens.add({
targets: [this],
duration: 350,
alpha: show ? 1 : 0,
y: this.defaultPosition.y,
scale: this.defaultScale,
ease: Easing.BackEaseOut,
onComplete: () => {
this.showAnimationTween = undefined;
},
});
}
public isShown(): boolean {
return this.shown;
}
}
+17
View File
@@ -17,6 +17,7 @@ import { Unsubscriber, Writable, writable } from "svelte/store";
import { createColorStore } from "../../Stores/OutlineColorStore";
import type { OutlineableInterface } from "../Game/OutlineableInterface";
import type CancelablePromise from "cancelable-promise";
import { TalkIcon } from "../Components/TalkIcon";
const playerNameY = -25;
@@ -33,6 +34,7 @@ const interactiveRadius = 35;
export abstract class Character extends Container implements OutlineableInterface {
private bubble: SpeechBubble | null = null;
private readonly playerNameText: Text;
private readonly talkIcon: TalkIcon;
public playerName: string;
public sprites: Map<string, Sprite>;
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
@@ -102,6 +104,17 @@ export abstract class Character extends Container implements OutlineableInterfac
fontSize: 35,
},
});
this.talkIcon = new TalkIcon(scene, 0, -45);
this.add(this.talkIcon);
if (isClickable) {
this.setInteractive({
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
useHandCursor: true,
});
}
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerNameText);
@@ -200,6 +213,10 @@ export abstract class Character extends Container implements OutlineableInterfac
});
}
public showTalkIcon(show: boolean = true, forceClose: boolean = false): void {
this.talkIcon.show(show, forceClose);
}
public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void {
if (typeof texturePromise !== "undefined") {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
+2 -1
View File
@@ -7,6 +7,7 @@ import type { PlayerAnimationDirections } from "../Player/Animation";
import type { Unsubscriber } from "svelte/store";
import type { ActivatableInterface } from "../Game/ActivatableInterface";
import type CancelablePromise from "cancelable-promise";
import LL from "../../i18n/i18n-svelte";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
@@ -107,7 +108,7 @@ export class RemotePlayer extends Character implements ActivatableInterface {
private registerDefaultActionsMenuActions(): void {
if (this.visitCardUrl) {
this.registeredActions.push({
actionName: "Visiting Card",
actionName: LL.woka.menu.businessCard(),
callback: () => {
requestVisitCardsStore.set(this.visitCardUrl);
actionsMenuStore.clear();
+38
View File
@@ -133,6 +133,18 @@ export class GameMap {
return grid;
}
public getWalkingCostGrid(): number[][] {
const grid: number[][] = [];
for (let y = 0; y < this.map.height; y += 1) {
const row: number[] = [];
for (let x = 0; x < this.map.width; x += 1) {
row.push(this.getWalkingCostAt(x, y));
}
grid.push(row);
}
return grid;
}
public getTileDimensions(): { width: number; height: number } {
return { width: this.map.tilewidth, height: this.map.tileheight };
}
@@ -356,6 +368,32 @@ export class GameMap {
return false;
}
private getWalkingCostAt(x: number, y: number): number {
const bigCost = 100;
for (const layer of this.phaserLayers) {
if (!layer.visible) {
continue;
}
const tile = layer.getTileAt(x, y);
if (!tile) {
continue;
}
if (
tile &&
(tile.properties[GameMapProperties.EXIT_URL] || tile.properties[GameMapProperties.EXIT_SCENE_URL])
) {
return bigCost;
}
for (const property of layer.layer.properties) {
//@ts-ignore
if (property.name && property.name === "exitUrl") {
return bigCost;
}
}
}
return 0;
}
private triggerAllProperties(): void {
const newProps = this.getProperties(this.key ?? 0);
const oldProps = this.lastProperties;
@@ -24,6 +24,7 @@ export enum GameMapProperties {
OPEN_WEBSITE_POSITION = "openWebsitePosition",
OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger",
OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage",
OPEN_WEBSITE_HINT = "openWebsiteHint",
PLAY_AUDIO = "playAudio",
PLAY_AUDIO_LOOP = "playAudioLoop",
READABLE_BY = "readableBy",
@@ -194,6 +194,7 @@ export class GameMapPropertiesListener {
let websitePositionProperty: number | undefined;
let websiteTriggerProperty: string | undefined;
let websiteTriggerMessageProperty: string | undefined;
let websiteHintProperty: string | undefined;
layer.properties.forEach((property) => {
switch (property.name) {
@@ -218,6 +219,9 @@ export class GameMapPropertiesListener {
case GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE:
websiteTriggerMessageProperty = property.value as string | undefined;
break;
case GameMapProperties.OPEN_WEBSITE_HINT:
websiteHintProperty = property.value as string | undefined;
break;
}
});
@@ -251,7 +255,8 @@ export class GameMapPropertiesListener {
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
false
false,
websiteHintProperty
);
coWebsiteOpen.coWebsite = coWebsite;
@@ -284,7 +289,8 @@ export class GameMapPropertiesListener {
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
false
false,
websiteHintProperty
);
coWebsiteOpen.coWebsite = coWebsite;
+41 -9
View File
@@ -92,6 +92,7 @@ import { followUsersColorStore } from "../../Stores/FollowStore";
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte";
import { i18nJson } from "../../i18n/locales";
import { localVolumeStore } from "../../Stores/MediaStore";
import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
@@ -173,6 +174,9 @@ export class GameScene extends DirtyScene {
private peerStoreUnsubscribe!: Unsubscriber;
private emoteUnsubscribe!: Unsubscriber;
private emoteMenuUnsubscribe!: Unsubscriber;
private volumeStoreUnsubscribers: Map<number, Unsubscriber> = new Map<number, Unsubscriber>();
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
private followUsersColorStoreUnsubscribe!: Unsubscriber;
private biggestAvailableAreaStoreUnsubscribe!: () => void;
@@ -250,6 +254,7 @@ export class GameScene extends DirtyScene {
loadCustomTexture(this.load, texture).catch((e) => console.error(e));
}
}
this.load.image("iconTalk", "/resources/icons/icon_talking.png");
if (touchScreenManager.supportTouchScreen) {
this.load.image(joystickBaseKey, joystickBaseImg);
@@ -585,6 +590,7 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionGrid(),
this.gameMap.getWalkingCostGrid(),
this.gameMap.getTileDimensions()
);
@@ -600,12 +606,6 @@ export class GameScene extends DirtyScene {
waScaleManager
);
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionGrid(),
this.gameMap.getTileDimensions()
);
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
biggestAvailableAreaStore.recompute();
@@ -659,14 +659,45 @@ export class GameScene extends DirtyScene {
this.connect();
}
const talkIconVolumeTreshold = 10;
let oldPeerNumber = 0;
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
this.volumeStoreUnsubscribers.forEach((unsubscribe) => unsubscribe());
this.volumeStoreUnsubscribers.clear();
for (const [key, videoStream] of peers) {
this.volumeStoreUnsubscribers.set(
key,
videoStream.volumeStore.subscribe((volume) => {
if (volume) {
this.MapPlayersByKey.get(key)?.showTalkIcon(volume > talkIconVolumeTreshold);
}
})
);
}
const newPeerNumber = peers.size;
if (newPeerNumber > oldPeerNumber) {
this.playSound("audio-webrtc-in");
} else if (newPeerNumber < oldPeerNumber) {
this.playSound("audio-webrtc-out");
}
if (newPeerNumber > 0) {
if (!this.localVolumeStoreUnsubscriber) {
this.localVolumeStoreUnsubscriber = localVolumeStore.subscribe((volume) => {
if (volume) {
this.CurrentPlayer.showTalkIcon(volume > talkIconVolumeTreshold);
}
});
}
} else {
this.CurrentPlayer.showTalkIcon(false, true);
this.MapPlayersByKey.forEach((remotePlayer) => remotePlayer.showTalkIcon(false, true));
if (this.localVolumeStoreUnsubscriber) {
this.localVolumeStoreUnsubscriber();
this.localVolumeStoreUnsubscriber = undefined;
}
}
oldPeerNumber = newPeerNumber;
});
@@ -1218,7 +1249,8 @@ export class GameScene extends DirtyScene {
openCoWebsite.allowApi,
openCoWebsite.allowPolicy,
openCoWebsite.widthPercent,
openCoWebsite.closable ?? true
openCoWebsite.closable ?? true,
openCoWebsite.hint
);
if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
@@ -1450,7 +1482,7 @@ export class GameScene extends DirtyScene {
phaserLayer.setCollisionByProperty({ collides: true }, visible);
} else {
const phaserLayers = this.gameMap.findPhaserLayers(layerName + "/");
if (phaserLayers === []) {
if (phaserLayers.length === 0) {
console.warn(
'Could not find layer with name that contains "' +
layerName +
@@ -1463,7 +1495,7 @@ export class GameScene extends DirtyScene {
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
}
}
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid(), this.gameMap.getWalkingCostGrid());
this.markDirty();
}
+3 -3
View File
@@ -15,7 +15,7 @@ export type EmbedScreen =
};
function createHighlightedEmbedScreenStore() {
const { subscribe, set, update } = writable<EmbedScreen | null>(null);
const { subscribe, set, update } = writable<EmbedScreen | undefined>(undefined);
return {
subscribe,
@@ -23,7 +23,7 @@ function createHighlightedEmbedScreenStore() {
set(embedScreen);
},
removeHighlight: () => {
set(null);
set(undefined);
},
toggleHighlight: (embedScreen: EmbedScreen) => {
update((currentEmbedScreen) =>
@@ -36,7 +36,7 @@ function createHighlightedEmbedScreenStore() {
currentEmbedScreen.type === "streamable" &&
embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId)
? embedScreen
: null
: undefined
);
},
};
+37
View File
@@ -10,6 +10,8 @@ import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
import { peerStore } from "./PeerStore";
import { privacyShutdownStore } from "./PrivacyShutdownStore";
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { AudioContext } from "standardized-audio-context";
/**
* A store that contains the camera state requested by the user (on or off).
@@ -541,6 +543,41 @@ export const obtainedMediaConstraintStore = derived<Readable<MediaStreamConstrai
}
);
export const localVolumeStore = readable<number | undefined>(undefined, (set) => {
let timeout: ReturnType<typeof setTimeout>;
const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => {
clearInterval(timeout);
if (localStreamStoreValue.type === "error") {
set(undefined);
return;
}
const mediaStream = localStreamStoreValue.stream;
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
set(undefined);
return;
}
const soundMeter = new SoundMeter(mediaStream);
let error = false;
timeout = setInterval(() => {
try {
set(soundMeter.getVolume());
} catch (err) {
if (!error) {
console.error(err);
error = true;
}
}
}, 100);
});
return () => {
unsubscribe();
clearInterval(timeout);
};
});
/**
* Device list
*/
+18 -2
View File
@@ -8,19 +8,27 @@ export class PathfindingManager {
private grid: number[][];
private tileDimensions: { width: number; height: number };
constructor(scene: Phaser.Scene, collisionsGrid: number[][], tileDimensions: { width: number; height: number }) {
constructor(
scene: Phaser.Scene,
collisionsGrid: number[][],
walkingCostGrid: number[][],
tileDimensions: { width: number; height: number }
) {
this.scene = scene;
this.easyStar = new EasyStar.js();
this.easyStar.enableDiagonals();
this.easyStar.disableCornerCutting();
this.grid = collisionsGrid;
this.tileDimensions = tileDimensions;
this.setEasyStarGrid(collisionsGrid);
this.setWalkingCostGrid(walkingCostGrid);
}
public setCollisionGrid(collisionGrid: number[][]): void {
public setCollisionGrid(collisionGrid: number[][], walkingCostGrid: number[][]): void {
this.setEasyStarGrid(collisionGrid);
this.setWalkingCostGrid(walkingCostGrid);
}
public async findPath(
@@ -115,6 +123,14 @@ export class PathfindingManager {
this.easyStar.setAcceptableTiles([0]); // zeroes are walkable
}
private setWalkingCostGrid(grid: number[][]): void {
for (let y = 0; y < grid.length; y += 1) {
for (let x = 0; x < grid[y].length; x += 1) {
this.easyStar.setAdditionalPointCost(x, y, grid[y][x]);
}
}
}
private logGridToTheConsole(grid: number[][]): void {
let rowNumber = 0;
for (const row of grid) {
+1
View File
@@ -12,6 +12,7 @@ export interface CoWebsite {
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined;
getWidthPercent(): number | undefined;
isClosable(): boolean;
getHint(): string;
load(): CancelablePromise<HTMLIFrameElement>;
unload(): Promise<void>;
}
@@ -14,8 +14,9 @@ export class SimpleCoWebsite implements CoWebsite {
protected allowPolicy?: string;
protected widthPercent?: number;
protected closable: boolean;
protected hint: string;
constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean) {
constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean, hint?: string) {
this.id = coWebsiteManager.generateUniqueId();
this.url = url;
this.state = writable("asleep" as CoWebsiteState);
@@ -23,6 +24,7 @@ export class SimpleCoWebsite implements CoWebsite {
this.allowPolicy = allowPolicy;
this.widthPercent = widthPercent;
this.closable = closable ?? false;
this.hint = hint ?? '';
}
getId(): string {
@@ -57,6 +59,10 @@ export class SimpleCoWebsite implements CoWebsite {
return this.closable;
}
getHint(): string {
return this.hint;
}
load(): CancelablePromise<HTMLIFrameElement> {
this.loadIframe = new CancelablePromise((resolve, reject, cancel) => {
this.state.set("loading");
+28 -5
View File
@@ -159,9 +159,17 @@ class CoWebsiteManager {
});
buttonSwipe.addEventListener("click", () => {
const mainCoWebsite = this.getMainCoWebsite();
const highlightedEmbed = get(highlightedEmbedScreen);
if (highlightedEmbed?.type === "cowebsite") {
this.goToMain(highlightedEmbed.embed);
if (mainCoWebsite) {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: mainCoWebsite,
});
}
}
});
}
@@ -553,6 +561,13 @@ class CoWebsiteManager {
coWebsites.remove(coWebsite);
coWebsites.add(coWebsite, 0);
if (mainCoWebsite) {
const iframe = mainCoWebsite.getIframe();
if (iframe) {
iframe.style.display = "block";
}
}
if (
isMediaBreakpointDown("lg") &&
get(embedScreenLayout) === LayoutMode.Presentation &&
@@ -596,12 +611,20 @@ class CoWebsiteManager {
.load()
.then(() => {
const mainCoWebsite = this.getMainCoWebsite();
if (mainCoWebsite && mainCoWebsite.getId() === coWebsite.getId()) {
this.openMain();
const highlightedEmbed = get(highlightedEmbedScreen);
if (mainCoWebsite) {
if (mainCoWebsite.getId() === coWebsite.getId()) {
this.openMain();
setTimeout(() => {
this.fire();
}, animationTime);
setTimeout(() => {
this.fire();
}, animationTime);
} else if (!highlightedEmbed) {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: coWebsite,
});
}
}
this.resizeAllIframes();
})
+32
View File
@@ -10,6 +10,9 @@ import { playersStore } from "../Stores/PlayersStore";
import { chatMessagesStore, newChatMessageSubject } from "../Stores/ChatStore";
import { getIceServersConfig } from "../Components/Video/utils";
import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { AudioContext } from "standardized-audio-context";
import { Console } from "console";
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
@@ -33,6 +36,7 @@ export class VideoPeer extends Peer {
private onBlockSubscribe: Subscription;
private onUnBlockSubscribe: Subscription;
public readonly streamStore: Readable<MediaStream | null>;
public readonly volumeStore: Readable<number | undefined>;
public readonly statusStore: Readable<PeerStatus>;
public readonly constraintsStore: Readable<ObtainedMediaStreamConstraints | null>;
private newMessageSubscribtion: Subscription | undefined;
@@ -69,6 +73,34 @@ export class VideoPeer extends Peer {
};
});
this.volumeStore = readable<number | undefined>(undefined, (set) => {
let timeout: ReturnType<typeof setTimeout>;
const unsubscribe = this.streamStore.subscribe((mediaStream) => {
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
set(undefined);
return;
}
const soundMeter = new SoundMeter(mediaStream);
let error = false;
timeout = setInterval(() => {
try {
set(soundMeter.getVolume());
} catch (err) {
if (!error) {
console.error(err);
error = true;
}
}
}, 100);
});
return () => {
unsubscribe();
clearInterval(timeout);
};
});
this.constraintsStore = readable<ObtainedMediaStreamConstraints | null>(null, (set) => {
const onData = (chunk: Buffer) => {
const message = JSON.parse(chunk.toString("utf8"));
+3
View File
@@ -15,6 +15,9 @@ const woka: NonNullable<Translation["woka"]> = {
continue: "Auswählen",
customize: "Bearbeite dein Avatar",
},
menu: {
businessCard: "Visitenkarte",
},
};
export default woka;
+3
View File
@@ -15,6 +15,9 @@ const woka: BaseTranslation = {
continue: "Continue",
customize: "Customize your WOKA",
},
menu: {
businessCard: "Business Card",
},
};
export default woka;
@@ -1,4 +1,8 @@
//TextGlobalMessage
// TODO: load on demand with TextGlobalMessage component
@import "quill/dist/quill.snow.css";
section.section-input-send-text {
--height-toolbar: 20%;
height: 100%;
+4
View File
@@ -43,6 +43,10 @@
flex: 1;
justify-content: start;
.nes-btn {
font-size: 16px;
}
#cowebsite-swipe {
display: none;
img {
+1
View File
@@ -1,4 +1,5 @@
@import "~@fontsource/press-start-2p/index.css";
@import "~@16bits/nes.css/css/nes.min.css";
*{
font-family: "Press Start 2P",monospace;
-1
View File
@@ -2,7 +2,6 @@ body {
overflow: hidden;
font-size: 10px;
}
body button:focus,
body img:focus,
body input:focus {
+8 -3
View File
@@ -2,6 +2,11 @@
# yarn lockfile v1
"@16bits/nes.css@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@16bits/nes.css/-/nes.css-2.3.2.tgz#e69db834119b33ae8d3cb044f106a07a17cadd6f"
integrity sha512-nEM5PIth+Bab5JSOa4uUR+PMNUsNTYxA55oVlG3gXI/4LoYtWS767Uv9Pu/KCbHXVvnIjt4ZXt13kZw3083qTw==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
@@ -2795,9 +2800,9 @@ focus-trap@^5.1.0:
xtend "^4.0.1"
follow-redirects@^1.0.0, follow-redirects@^1.14.0:
version "1.14.7"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
version "1.14.8"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
for-in@^1.0.2:
version "1.0.2"