Merge branch 'develop' into changeRegisterAccess

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

# Conflicts:
#	front/src/Connexion/ConnectionManager.ts
#	front/vite.config.ts
This commit is contained in:
Gregoire Parant 2022-03-29 13:30:40 +02:00
commit dce1ece837
108 changed files with 1637 additions and 1318 deletions

View File

@ -55,7 +55,8 @@
"query-string": "^6.13.3",
"redis": "^3.1.2",
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
"uuidv4": "^6.0.7"
"uuidv4": "^6.0.7",
"zod": "^3.12.0"
},
"devDependencies": {
"@types/busboy": "^0.2.3",

View File

@ -6,6 +6,7 @@ import {
EmoteCallback,
EntersCallback,
LeavesCallback,
LockGroupCallback,
MovesCallback,
PlayerDetailsUpdatedCallback,
} from "_Model/Zone";
@ -44,7 +45,7 @@ export class GameRoom {
// Users, sorted by ID
private readonly users = new Map<number, User>();
private readonly usersByUuid = new Map<string, User>();
private readonly groups = new Set<Group>();
private readonly groups: Map<number, Group> = new Map<number, Group>();
private readonly admins = new Set<Admin>();
private itemsState = new Map<number, unknown>();
@ -66,6 +67,7 @@ export class GameRoom {
onMoves: MovesCallback,
onLeaves: LeavesCallback,
onEmote: EmoteCallback,
onLockGroup: LockGroupCallback,
onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
) {
// A zone is 10 sprites wide.
@ -76,6 +78,7 @@ export class GameRoom {
onMoves,
onLeaves,
onEmote,
onLockGroup,
onPlayerDetailsUpdated
);
}
@ -90,6 +93,7 @@ export class GameRoom {
onMoves: MovesCallback,
onLeaves: LeavesCallback,
onEmote: EmoteCallback,
onLockGroup: LockGroupCallback,
onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
): Promise<GameRoom> {
const mapDetails = await GameRoom.getMapDetails(roomUrl);
@ -105,6 +109,7 @@ export class GameRoom {
onMoves,
onLeaves,
onEmote,
onLockGroup,
onPlayerDetailsUpdated
);
@ -244,7 +249,7 @@ export class GameRoom {
this.disconnectCallback,
this.positionNotifier
);
this.groups.add(group);
this.groups.set(group.getId(), group);
}
}
} else {
@ -328,7 +333,7 @@ export class GameRoom {
this.disconnectCallback,
this.positionNotifier
);
this.groups.add(newGroup);
this.groups.set(newGroup.getId(), newGroup);
} else {
this.leaveGroup(user);
}
@ -375,10 +380,10 @@ export class GameRoom {
group.leave(user);
if (group.isEmpty()) {
group.destroy();
if (!this.groups.has(group)) {
if (!this.groups.has(group.getId())) {
throw new Error(`Could not find group ${group.getId()} referenced by user ${user.id} in World.`);
}
this.groups.delete(group);
this.groups.delete(group.getId());
//todo: is the group garbage collected?
} else {
group.updatePosition();
@ -418,7 +423,7 @@ export class GameRoom {
});
this.groups.forEach((group: Group) => {
if (group.isFull()) {
if (group.isFull() || group.isLocked()) {
return;
}
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition());
@ -544,6 +549,10 @@ export class GameRoom {
this.positionNotifier.emitEmoteEvent(user, emoteEventMessage);
}
public emitLockGroupEvent(user: User, groupId: number) {
this.positionNotifier.emitLockGroupEvent(user, groupId);
}
public addRoomListener(socket: RoomSocket) {
this.roomListeners.add(socket);
}
@ -657,4 +666,8 @@ export class GameRoom {
const variablesManager = await this.getVariableManager();
return variablesManager.getVariablesForTags(tags);
}
public getGroupById(id: number): Group | undefined {
return this.groups.get(id);
}
}

View File

@ -14,6 +14,7 @@ export class Group implements Movable {
private x!: number;
private y!: number;
private wasDestroyed: boolean = false;
private locked: boolean = false;
private roomId: string;
private currentZone: Zone | null = null;
/**
@ -141,15 +142,19 @@ export class Group implements Movable {
return this.users.size >= MAX_PER_GROUP;
}
isLocked(): boolean {
return this.locked;
}
isEmpty(): boolean {
return this.users.size <= 1;
}
join(user: User): void {
// Broadcast on the right event
this.connectCallback(user, this);
this.users.add(user);
user.group = this;
this.connectCallback(user, this);
}
leave(user: User): void {
@ -167,6 +172,10 @@ export class Group implements Movable {
this.disconnectCallback(user, this);
}
lock(lock: boolean = true): void {
this.locked = lock;
}
/**
* Let's kick everybody out.
* Usually used when there is only one user left.

View File

@ -12,6 +12,7 @@ import {
EmoteCallback,
EntersCallback,
LeavesCallback,
LockGroupCallback,
MovesCallback,
PlayerDetailsUpdatedCallback,
Zone,
@ -50,6 +51,7 @@ export class PositionNotifier {
private onUserMoves: MovesCallback,
private onUserLeaves: LeavesCallback,
private onEmote: EmoteCallback,
private onLockGroup: LockGroupCallback,
private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
) {}
@ -111,6 +113,7 @@ export class PositionNotifier {
this.onUserMoves,
this.onUserLeaves,
this.onEmote,
this.onLockGroup,
this.onPlayerDetailsUpdated,
i,
j
@ -137,6 +140,12 @@ export class PositionNotifier {
zone.emitEmoteEvent(emoteEventMessage);
}
public emitLockGroupEvent(user: User, groupId: number) {
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.emitLockGroupEvent(groupId);
}
public *getAllUsersInSquareAroundZone(zone: Zone): Generator<User> {
const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y);
for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) {

View File

@ -13,6 +13,7 @@ export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: Z
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void;
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
export type LockGroupCallback = (groupId: number, listener: ZoneSocket) => void;
export type PlayerDetailsUpdatedCallback = (
playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage,
listener: ZoneSocket
@ -27,6 +28,7 @@ export class Zone {
private onMoves: MovesCallback,
private onLeaves: LeavesCallback,
private onEmote: EmoteCallback,
private onLockGroup: LockGroupCallback,
private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback,
public readonly x: number,
public readonly y: number
@ -108,6 +110,12 @@ export class Zone {
}
}
public emitLockGroupEvent(groupId: number) {
for (const listener of this.listeners) {
this.onLockGroup(groupId, listener);
}
}
public updatePlayerDetails(user: User, playerDetails: SetPlayerDetailsMessage) {
const playerDetailsUpdatedMessage = new PlayerDetailsUpdatedMessage();
playerDetailsUpdatedMessage.setUserid(user.id);

View File

@ -29,6 +29,7 @@ import {
WebRtcSignalToServerMessage,
WorldFullWarningToRoomMessage,
ZoneMessage,
LockGroupPromptMessage,
} from "./Messages/generated/messages_pb";
import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc";
import { socketManager } from "./Services/SocketManager";
@ -135,6 +136,12 @@ const roomManager: IRoomManagerServer = {
user,
message.getFollowabortmessage() as FollowAbortMessage
);
} else if (message.hasLockgrouppromptmessage()) {
socketManager.handleLockGroupPromptMessage(
room,
user,
message.getLockgrouppromptmessage() as LockGroupPromptMessage
);
} else if (message.hasSendusermessage()) {
const sendUserMessage = message.getSendusermessage();
socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage);

View File

@ -38,6 +38,9 @@ import {
SubToPusherRoomMessage,
SetPlayerDetailsMessage,
PlayerDetailsUpdatedMessage,
GroupUsersUpdateMessage,
LockGroupPromptMessage,
RoomMessage,
} from "../Messages/generated/messages_pb";
import { User, UserSocket } from "../Model/User";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
@ -68,7 +71,6 @@ function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): vo
// TODO: should we batch those every 100ms?
const batchMessage = new BatchToPusherMessage();
batchMessage.addPayload(subMessage);
socket.write(batchMessage);
}
@ -266,18 +268,28 @@ export class SocketManager {
if (roomPromise === undefined) {
roomPromise = GameRoom.create(
roomId,
(user: User, group: Group) => this.joinWebRtcRoom(user, group),
(user: User, group: Group) => this.disConnectedUser(user, group),
(user: User, group: Group) => {
this.joinWebRtcRoom(user, group);
this.sendGroupUsersUpdateToGroupMembers(group);
},
(user: User, group: Group) => {
this.disConnectedUser(user, group);
this.sendGroupUsersUpdateToGroupMembers(group);
},
MINIMUM_DISTANCE,
GROUP_RADIUS,
(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) =>
this.onZoneEnter(thing, fromZone, listener),
(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => {
this.onZoneEnter(thing, fromZone, listener);
},
(thing: Movable, position: PositionInterface, listener: ZoneSocket) =>
this.onClientMove(thing, position, listener),
(thing: Movable, newZone: Zone | null, listener: ZoneSocket) =>
this.onClientLeave(thing, newZone, listener),
(emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) =>
this.onEmote(emoteEventMessage, listener),
(groupId: number, listener: ZoneSocket) => {
void this.onLockGroup(groupId, listener, roomPromise);
},
(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ZoneSocket) =>
this.onPlayerDetailsUpdated(playerDetailsUpdatedMessage, listener)
)
@ -381,10 +393,24 @@ export class SocketManager {
emitZoneMessage(subMessage, client);
}
private async onLockGroup(
groupId: number,
client: ZoneSocket,
roomPromise: PromiseLike<GameRoom> | undefined
): Promise<void> {
if (!roomPromise) {
return;
}
const group = (await roomPromise).getGroupById(groupId);
if (!group) {
return;
}
this.emitCreateUpdateGroupEvent(client, null, group);
}
private onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, client: ZoneSocket) {
const subMessage = new SubToPusherMessage();
subMessage.setPlayerdetailsupdatedmessage(playerDetailsUpdatedMessage);
emitZoneMessage(subMessage, client);
}
@ -398,6 +424,7 @@ export class SocketManager {
groupUpdateMessage.setPosition(pointMessage);
groupUpdateMessage.setGroupsize(group.getSize);
groupUpdateMessage.setFromzone(this.toProtoZone(fromZone));
groupUpdateMessage.setLocked(group.isLocked());
const subMessage = new SubToPusherMessage();
subMessage.setGroupupdatezonemessage(groupUpdateMessage);
@ -413,7 +440,6 @@ export class SocketManager {
const subMessage = new SubToPusherMessage();
subMessage.setGroupleftzonemessage(groupDeleteMessage);
emitZoneMessage(subMessage, client);
//user.emitInBatch(subMessage);
}
@ -425,7 +451,6 @@ export class SocketManager {
const subMessage = new SubToPusherMessage();
subMessage.setUserleftzonemessage(userLeftMessage);
emitZoneMessage(subMessage, client);
}
@ -439,6 +464,19 @@ export class SocketManager {
return undefined;
}
private sendGroupUsersUpdateToGroupMembers(group: Group) {
const groupUserUpdateMessage = new GroupUsersUpdateMessage();
groupUserUpdateMessage.setGroupid(group.getId());
groupUserUpdateMessage.setUseridsList(group.getUsers().map((user) => user.id));
const clientMessage = new ServerToClientMessage();
clientMessage.setGroupusersupdatemessage(groupUserUpdateMessage);
group.getUsers().forEach((currentUser: User) => {
currentUser.socket.write(clientMessage);
});
}
private joinWebRtcRoom(user: User, group: Group) {
for (const otherUser of group.getUsers()) {
if (user === otherUser) {
@ -634,6 +672,7 @@ export class SocketManager {
const groupUpdateMessage = new GroupUpdateZoneMessage();
groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
groupUpdateMessage.setLocked(thing.isLocked());
const subMessage = new SubToPusherMessage();
subMessage.setGroupupdatezonemessage(groupUpdateMessage);
@ -870,6 +909,15 @@ export class SocketManager {
leader?.delFollower(user);
}
}
handleLockGroupPromptMessage(room: GameRoom, user: User, message: LockGroupPromptMessage) {
const group = user.group;
if (!group) {
return;
}
group.lock(message.getLock());
room.emitLockGroupEvent(user, group.getId());
}
}
export const socketManager = new SocketManager();

View File

@ -52,6 +52,7 @@ describe("GameRoom", () => {
() => {},
() => {},
emote,
() => {},
() => {}
);
@ -88,6 +89,7 @@ describe("GameRoom", () => {
() => {},
() => {},
emote,
() => {},
() => {}
);
@ -128,6 +130,7 @@ describe("GameRoom", () => {
() => {},
() => {},
emote,
() => {},
() => {}
);

View File

@ -25,6 +25,7 @@ describe("PositionNotifier", () => {
leaveTriggered = true;
},
() => {},
() => {},
() => {}
);
@ -132,6 +133,7 @@ describe("PositionNotifier", () => {
leaveTriggered = true;
},
() => {},
() => {},
() => {}
);

View File

@ -2258,3 +2258,8 @@ yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
zod@^3.12.0:
version "3.14.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2"
integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw==

View File

@ -45,7 +45,7 @@ server {
rewrite ^/jwt /index.html break;
}
location ~ ^/[@_]/ {
location ~ ^/[@_*]/ {
try_files $uri $uri/ /index.html;
}
}

View File

@ -17,7 +17,7 @@
"eslint": "^8.4.1",
"eslint-plugin-svelte3": "^3.2.1",
"jasmine": "^3.5.0",
"lint-staged": "^11.0.0",
"lint-staged": "^12.3.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.1",
"prettier-plugin-svelte": "^2.5.0",
@ -57,6 +57,7 @@
"simple-peer": "^9.11.0",
"socket.io-client": "^2.3.0",
"standardized-audio-context": "^25.2.4",
"ts-deferred": "^1.0.4",
"ts-proto": "^1.96.0",
"typesafe-i18n": "^2.59.0",
"uuidv4": "^6.2.10",

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg2985" version="1.1" inkscape:version="0.48.4 r9939" width="485.33627" height="485.33627" sodipodi:docname="600px-France_road_sign_B1j.svg[1].png">
<metadata id="metadata2991">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs2989"/>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1272" inkscape:window-height="745" id="namedview2987" showgrid="false" inkscape:snap-global="true" inkscape:snap-grids="true" inkscape:snap-bbox="true" inkscape:bbox-paths="true" inkscape:bbox-nodes="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:object-paths="true" inkscape:snap-intersection-paths="true" inkscape:object-nodes="true" inkscape:snap-smooth-nodes="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-center="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:zoom="0.59970176" inkscape:cx="390.56499" inkscape:cy="244.34365" inkscape:window-x="86" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="layer1">
<inkscape:grid type="xygrid" id="grid2995" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="-57.33186px" originy="-57.33186px"/>
</sodipodi:namedview>
<g inkscape:groupmode="layer" id="layer1" inkscape:label="1" style="display:inline" transform="translate(-57.33186,-57.33186)">
<path sodipodi:type="arc" style="color:#000000;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2997" sodipodi:cx="300" sodipodi:cy="300" sodipodi:rx="240" sodipodi:ry="240" d="M 540,300 C 540,432.54834 432.54834,540 300,540 167.45166,540 60,432.54834 60,300 60,167.45166 167.45166,60 300,60 432.54834,60 540,167.45166 540,300 z" transform="matrix(1.0058783,0,0,1.0058783,-1.76349,-1.76349)"/>
<path sodipodi:type="arc" style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4005" sodipodi:cx="304.75" sodipodi:cy="214.75" sodipodi:rx="44.75" sodipodi:ry="44.75" d="m 349.5,214.75 c 0,24.71474 -20.03526,44.75 -44.75,44.75 -24.71474,0 -44.75,-20.03526 -44.75,-44.75 0,-24.71474 20.03526,-44.75 44.75,-44.75 24.71474,0 44.75,20.03526 44.75,44.75 z" transform="matrix(5.1364411,0,0,5.1364411,-1265.3304,-803.05073)"/>
<rect style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="rect4001" width="345" height="80.599998" x="127.5" y="259.70001"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 188.149 188.149" style="enable-background:new 0 0 188.149 188.149;" xml:space="preserve">
<g>
<g>
<defs>
<circle id="SVGID_1_" cx="94.075" cy="94.075" r="94.074"/>
</defs>
<use xlink:href="#SVGID_1_" style="overflow:visible;fill:#4AC8EB;"/>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_2_);">
<path style="fill:#A57561;" d="M148.572,197.629v0.01H39.507v-0.01c0-15.124,6.147-28.809,16.09-38.679
c0.463-0.463,0.926-0.905,1.408-1.347c1.43-1.326,2.931-2.57,4.493-3.732c1.028-0.771,2.098-1.491,3.177-2.189
c1.08-0.699,2.19-1.357,3.331-1.975c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761
c1.295-0.761,2.519-1.624,3.69-2.56c4.966-3.948,8.812-9.254,11.001-15.34v-0.011c1.306-3.629,2.016-7.546,2.016-11.639
l16.121,0.072c0,4.04,0.688,7.927,1.964,11.525c2.169,6.117,5.994,11.433,10.96,15.401c0.339,0.277,0.688,0.545,1.038,0.802
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.257,0.853,0.514,1.285,0.75
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.02,0.062,0.041,0.093,0.061c1.265,0.699,2.498,1.439,3.69,2.221
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
z"/>
<path style="fill:#EB6D4A;" d="M148.572,197.629H39.507c0-15.124,6.147-28.809,16.09-38.679c0.463-0.463,0.926-0.905,1.408-1.347
c1.43-1.326,2.931-2.581,4.493-3.742c1.028-0.762,2.098-1.491,3.177-2.18c1.08-0.699,2.19-1.357,3.331-1.975
c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761c1.295-0.761,2.519-1.624,3.69-2.56
c5.347,5.469,12.79,8.852,21.046,8.852c8.226,0,15.669-3.393,21.016-8.842c0.339,0.277,0.688,0.545,1.038,0.802
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.267,0.853,0.514,1.285,0.75
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.011,0.062,0.031,0.093,0.052c1.265,0.699,2.498,1.439,3.69,2.23
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
z"/>
<path style="fill:#A57561;" d="M52.183,46.81v34.117c0,28.977,25.437,52.466,41.857,52.466c16.421,0,41.858-23.489,41.858-52.466
V46.81H52.183z"/>
<path style="fill:#141720;" d="M52.183,76.823L52.183,76.823c2.063,0,3.734-1.671,3.734-3.733V49.356h-3.734V76.823z"/>
<path style="fill:#141720;" d="M135.899,76.823L135.899,76.823V49.356h-3.733V73.09
C132.165,75.152,133.836,76.823,135.899,76.823z"/>
<path style="fill:#141720;" d="M135.893,48.33c0,4.884-0.328,6.061-3.734,5.801c-0.137-2.367-17.141-4.296-38.111-4.296
c-20.985,0-37.989,1.929-38.126,4.296c-3.406,0.26-3.734-0.917-3.734-5.801c0-0.479,0.014-0.984,0.027-1.519
c0.52-12.052,7.318-34.582,41.833-34.582c34.5,0,41.299,22.53,41.818,34.582C135.879,47.346,135.893,47.852,135.893,48.33z"/>
</g>
<path style="clip-path:url(#SVGID_2_);fill:#FFFFFF;" d="M115.106,146.119c-3.517,10.826-10.601,21.539-21.036,30.299
c-10.436-8.76-17.509-19.473-21.025-30.299c0.052,0.02,0.113,0.041,0.165,0.061c5.531,4.955,12.852,7.979,20.86,7.979
c8.009,0,15.319-3.013,20.851-7.968C114.982,146.17,115.044,146.14,115.106,146.119z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1 +0,0 @@
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M512,84.85,427.15,0,256,171.15,84.85,0,0,84.85,171.15,256,0,427.15,84.85,512,256,340.85,427.15,512,512,427.15,340.85,256Z" transform="translate(0 0)"/></svg>

Before

Width:  |  Height:  |  Size: 319 B

View File

@ -1 +0,0 @@
<svg height="682pt" viewBox="-21 -47 682.66669 682" width="682pt" xmlns="http://www.w3.org/2000/svg"><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-279.152344l-185.183594 128.863281v-128.863281c-48.375-.164063-87.675781-39.574219-87.675781-87.988282v-283.972656c0-48.515625 39.472656-87.988281 87.988281-87.988281h464.023438c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffdb2d"/><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-232.109375v-459.949219h232.109375c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffaa20"/><g fill="#fff"><path d="m171.296875 131.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 211.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 291.167969h297.40625v37.5h-297.40625zm0 0"/></g><path d="m319.902344 131.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 211.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 291.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 451.7 512" style="enable-background:new 0 0 451.7 512;" xml:space="preserve">
<path d="M436.9,212.6L237.2,12.9c-11.7-11.7-30.7-11.7-42.4,0s-11.7,30.7,0,42.4L394.5,255c11.5,11.9,30.5,12.2,42.4,0.7
c11.9-11.5,12.2-30.5,0.7-42.4C437.4,213.1,437.2,212.8,436.9,212.6z"/>
<path d="M179.5,83.1l-1.5,7.5c-10.4,53-36,103.4-70.6,144.3l109,108.3c40.7-34.9,90.2-61.5,143.1-72.3l7.5-1.5L179.5,83.1z"/>
<path d="M87.4,257l-74.2,74.2c-17.6,17.6-17.6,46.1,0,63.6c0,0,0,0,0,0l42.4,42.4c17.6,17.6,46.1,17.6,63.6,0c0,0,0,0,0,0l74.2-74.2
L87.4,257z M98,373.7c-6.1,5.6-15.6,5.3-21.2-0.8c-5.4-5.8-5.4-14.7,0-20.5l21.2-21.2c6-5.8,15.5-5.6,21.2,0.4
c5.6,5.8,5.6,15,0,20.8L98,373.7z"/>
<path d="M256.1,445.3l20.4-20.4c17.6-17.6,17.6-46.1,0-63.6l-15.1-15.2c-8.4,5.7-16.4,11.7-24.2,18.3l18.1,18.1
c5.8,5.9,5.8,15.3,0,21.2l-20.7,20.8l-30.5-29.5l-42.4,42.4l68.1,65.9c11.7,11.7,30.7,11.7,42.4,0c11.7-11.7,11.7-30.7,0-42.4l0,0
L256.1,445.3z"/>
<path d="M316.7,0c-8.3,0-15,6.7-15,15v30c0,8.3,6.7,15,15,15c8.3,0,15-6.7,15-15V15C331.7,6.7,325,0,316.7,0z"/>
<path d="M436.7,120h-30c-8.3,0-15,6.7-15,15s6.7,15,15,15h30c8.3,0,15-6.7,15-15S445,120,436.7,120z"/>
<path d="M417.3,34.4c-5.9-5.9-15.4-5.9-21.2,0l-30,30c-6,5.8-6.1,15.3-0.4,21.2c5.8,6,15.3,6.1,21.2,0.4c0.1-0.1,0.2-0.2,0.4-0.4
l30-30C423.2,49.7,423.2,40.2,417.3,34.4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 377.87"><defs><style>.cls-1{fill:#ffda01;}</style></defs><path class="cls-1" d="M494.6,67.07H17.4A17.4,17.4,0,0,0,0,84.46V343.84a17.4,17.4,0,0,0,17.4,17.39H494.6A17.4,17.4,0,0,0,512,343.84V84.46a17.4,17.4,0,0,0-17.4-17.39Z" transform="translate(0 -67.07)"/><path class="cls-1" d="M355.42,414.75H296.35V391.42h-80.7v23.33H156.58a15.1,15.1,0,1,0,0,30.19H355.42a15.1,15.1,0,1,0,0-30.19Z" transform="translate(0 -67.07)"/></svg>

Before

Width:  |  Height:  |  Size: 512 B

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0" d="M163.2,351.1c-2.4-23.4-1.1-46.7,3.9-69.2c2.6-11-2.1-23.8-9.4-29.6l-36.8-39.7C142.5,160,170,126.2,216.8,95
l45.1,27.2c9,7.3,21.3,9.1,32,4.4c21.2-9.5,43.7-15.4,67.2-17.7c16.9-1.7,29.3-16.8,27.6-33.7L381.5,5
c-1.7-16.9-16.8-29.3-33.7-27.6C154-2.8,12.4,170.8,32.1,364.5c1.7,16.9,16.8,29.3,33.7,27.6l69.9-7.1
C152.5,383.1,164.9,368,163.2,351.1z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 804 B

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0" d="M32.6,256.8c17.3-15.9,36.8-28.8,57.8-38.3c10.4-4.5,17.8-15.9,18.1-25.3l9.8-53.2
c55-14.2,98.5-12.3,151.6,6.5l5.2,52.4c-0.5,11.6,5.4,22.5,15.6,28.3c20.3,11.3,38.5,25.8,54.4,43.2c11.5,12.5,31,13.4,43.5,1.9
l51.9-47.7c12.5-11.5,13.4-31,1.9-43.5c-131.8-143.5-355.6-153-499-21.3c-12.5,11.5-13.4,31-1.9,43.5l47.5,51.8
C0.6,267.4,20.1,268.3,32.6,256.8z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 810 B

View File

@ -1 +0,0 @@
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.48 56.48"><defs><style>.cls-1{fill:#e76e54;}.cls-2{fill:#fff;}</style></defs><path class="cls-1" d="M39.94,512H16.54L0,495.46v-23.4l16.54-16.54h23.4l16.54,16.54v23.4Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M33.54,485.52H23l-1.77-21.18H35.3Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M23,492.58H33.54v10.59H23Z" transform="translate(0 -455.52)"/></svg>

Before

Width:  |  Height:  |  Size: 477 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M481.508,210.336L68.414,38.926c-17.403-7.222-37.064-4.045-51.309,8.287C2.86,59.547-3.098,78.551,1.558,96.808
L38.327,241h180.026c8.284,0,15.001,6.716,15.001,15.001c0,8.284-6.716,15.001-15.001,15.001H38.327L1.558,415.193
c-4.656,18.258,1.301,37.262,15.547,49.595c14.274,12.357,33.937,15.495,51.31,8.287l413.094-171.409
C500.317,293.862,512,276.364,512,256.001C512,235.638,500.317,218.139,481.508,210.336z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 985 B

View File

@ -1 +0,0 @@
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 441.78"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M481.51,210.34,68.41,38.93A49.44,49.44,0,0,0,1.56,96.81L38.33,241h180a15,15,0,1,1,0,30h-180L1.56,415.19a49.43,49.43,0,0,0,66.86,57.88l413.09-171.4a49.44,49.44,0,0,0,0-91.33Z" transform="translate(0 -35.11)"/></svg>

Before

Width:  |  Height:  |  Size: 379 B

View File

@ -1 +0,0 @@
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 441.78"><defs><style>.cls-1{fill:#ffda01;}</style></defs><path class="cls-1" d="M481.51,210.34,68.41,38.93A49.44,49.44,0,0,0,1.56,96.81L38.33,241h180a15,15,0,1,1,0,30h-180L1.56,415.19a49.43,49.43,0,0,0,66.86,57.88l413.09-171.4a49.44,49.44,0,0,0,0-91.33Z" transform="translate(0 -35.11)"/></svg>

Before

Width:  |  Height:  |  Size: 382 B

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M453.6,237.3c-1.5-18.8-5.6-37-12.3-54.5l46.1-37l-75-93.6L366.5,89c-15.8-10.7-33-18.9-51.1-24.6V6.1h-120v58.7
c-19,6.1-36.8,15-53.2,26.5l-46.3-36L22.3,150l47.2,36.6c-6.3,17.5-10,35.8-11.2,54.5L0,255l27.9,116.7l41-9.8
c20.2-20.2,38.4-38.4,62.4-62.4C99.4,213,163.5,120.6,256.1,120.6c73.4,0,132.9,59.5,132.9,132.9c0,90.7-89,154.7-174.8,126.2
c-6.9,6.9-77.9,77.9-88.9,88.9l79.1,37.3l26.3-55.8c18.2,2.3,36.3,2.1,54.1-0.5l26.9,55.1L419.6,452l-26.9-54.9
c13.3-12.7,24.7-27,34.1-42.8l59,13.2L512,250.4L453.6,237.3z"/>
</g>
</g>
<g>
<g>
<path d="M336,219.4l-7.6-19.1L282.6,246L262,225.4l45.8-45.8l-19.1-7.6c-74.9-29.8-144.2,52.2-104.9,120.7
C82.6,393.9,169.1,307.5,47.6,429c-17,17,18.4,52.5,35.4,35.4c70.6-70.6,2-2,137.5-137.5C289.6,360.5,364.5,291,336,219.4z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.81"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M453.58,237.31a195.46,195.46,0,0,0-12.33-54.5l46.14-37-75-93.62L366.55,89a196.1,196.1,0,0,0-51.12-24.58V6.1h-120V64.75a195.85,195.85,0,0,0-53.23,26.53l-46.34-36L22.3,150l47.16,36.64a196.64,196.64,0,0,0-11.19,54.46L0,255,27.91,371.68l41-9.81,62.4-62.39C99.44,213,163.49,120.57,256.09,120.57A133,133,0,0,1,389,253.51c0,90.69-89,154.71-174.78,126.22l-88.88,88.89,79.1,37.28,26.31-55.81a199.26,199.26,0,0,0,54.12-.45l27,55.07L419.6,452l-26.88-54.93a197.42,197.42,0,0,0,34.06-42.84l59,13.22,26.2-117Z" transform="translate(0 -6.1)"/><path class="cls-1" d="M336,219.37l-7.61-19.14L282.55,246,262,225.44l45.79-45.78L288.63,172c-74.92-29.8-144.18,52.17-104.87,120.7C82.63,393.88,169.06,307.46,47.55,429,30.51,446,66,481.44,83,464.4c70.58-70.59,2-2,137.48-137.47C289.61,360.46,364.47,291,336,219.37Z" transform="translate(0 -6.1)"/></svg>

Before

Width:  |  Height:  |  Size: 994 B

View File

@ -1 +0,0 @@
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.81"><defs><style>.cls-1{fill:#ffda01;}</style></defs><path class="cls-1" d="M453.58,237.31a195.46,195.46,0,0,0-12.33-54.5l46.14-37-75-93.62L366.55,89a196.1,196.1,0,0,0-51.12-24.58V6.1h-120V64.75a195.85,195.85,0,0,0-53.23,26.53l-46.34-36L22.3,150l47.16,36.64a196.64,196.64,0,0,0-11.19,54.46L0,255,27.91,371.68l41-9.81,62.4-62.39C99.44,213,163.49,120.57,256.09,120.57A133,133,0,0,1,389,253.51c0,90.69-89,154.71-174.78,126.22l-88.88,88.89,79.1,37.28,26.31-55.81a199.26,199.26,0,0,0,54.12-.45l27,55.07L419.6,452l-26.88-54.93a197.42,197.42,0,0,0,34.06-42.84l59,13.22,26.2-117Z" transform="translate(0 -6.1)"/><path class="cls-1" d="M336,219.37l-7.61-19.14L282.55,246,262,225.44l45.79-45.78L288.63,172c-74.92-29.8-144.18,52.17-104.87,120.7C82.63,393.88,169.06,307.46,47.55,429,30.51,446,66,481.44,83,464.4c70.58-70.59,2-2,137.48-137.47C289.61,360.46,364.47,291,336,219.37Z" transform="translate(0 -6.1)"/></svg>

Before

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,5 +1,12 @@
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
import { get } from "svelte/store";
import { WorkAdventureDesktopApi } from "@wa-preload-app";
declare global {
interface Window {
WAD: WorkAdventureDesktopApi;
}
}
class DesktopApi {
isSilent: boolean = false;

View File

@ -77,6 +77,7 @@
height: max-content !important;
max-height: 40vh;
margin-top: 200px;
z-index: 425;
pointer-events: auto;
font-family: "Press Start 2P";

View File

@ -13,20 +13,20 @@
let unsubscriberFileStore: Unsubscriber | null = null;
let unsubscriberVolumeStore: Unsubscriber | null = null;
let decreaseWhileTalking: boolean = true;
let isAudioAllowed: boolean = true;
onMount(() => {
let volume = Math.min(localUserStore.getAudioPlayerVolume(), get(audioManagerVolumeStore).volume);
audioManagerVolumeStore.setVolume(volume);
audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted());
unsubscriberFileStore = audioManagerFileStore.subscribe((src) => {
unsubscriberFileStore = audioManagerFileStore.subscribe((src: string) => {
HTMLAudioPlayer.pause();
HTMLAudioPlayer.src = src;
HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop;
HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume;
HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted;
void HTMLAudioPlayer.play();
tryPlay();
});
unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => {
const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking;
@ -52,6 +52,16 @@
}
});
function tryPlay() {
void HTMLAudioPlayer.play()
.then(() => {
isAudioAllowed = true;
})
.catch(() => {
isAudioAllowed = false;
});
}
function updateVolumeUI() {
if (get(audioManagerVolumeStore).muted) {
audioPlayerVolumeIcon.classList.add("muted");
@ -90,73 +100,67 @@
audioPlayerVol.blur();
return false;
}
function setDecrease() {
audioManagerVolumeStore.setDecreaseWhileTalking(decreaseWhileTalking);
}
</script>
<div class="main-audio-manager nes-container is-rounded">
<div class="audio-manager-player-volume">
<span
id="audioplayer_volume_icon_playing"
alt="player volume"
bind:this={audioPlayerVolumeIcon}
on:click={onMute}
>
<svg
width="2em"
height="2em"
viewBox="0 0 16 16"
class="bi bi-volume-up"
fill="white"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
/>
<g id="audioplayer_volume_icon_playing_high">
<div class:hidden={!isAudioAllowed}>
<div class="audio-manager-player-volume">
<span id="audioplayer_volume_icon_playing" bind:this={audioPlayerVolumeIcon} on:click={onMute}>
<svg
width="2em"
height="2em"
viewBox="0 0 16 16"
class="bi bi-volume-up"
fill="white"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
fill-rule="evenodd"
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
/>
</g>
<g id="audioplayer_volume_icon_playing_mid">
<path
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
/>
</g>
<g id="audioplayer_volume_icon_playing_low">
<path
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
/>
</g>
</svg>
</span>
<input
type="range"
min="0"
max="1"
step="0.025"
bind:this={audioPlayerVol}
on:change={setVolume}
on:keydown={disallowKeys}
/>
</div>
<div class="audio-manager-reduce-conversation">
<label>
{$LL.audio.manager.reduce()}
<input type="checkbox" bind:checked={decreaseWhileTalking} on:change={setDecrease} />
</label>
<g id="audioplayer_volume_icon_playing_high">
<path
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
/>
</g>
<g id="audioplayer_volume_icon_playing_mid">
<path
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
/>
</g>
<g id="audioplayer_volume_icon_playing_low">
<path
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
/>
</g>
</svg>
</span>
<input
type="range"
min="0"
max="1"
step="0.025"
bind:this={audioPlayerVol}
on:change={setVolume}
on:keydown={disallowKeys}
/>
</div>
<section class="audio-manager-file">
<!-- svelte-ignore a11y-media-has-caption -->
<audio class="audio-manager-audioplayer" bind:this={HTMLAudioPlayer} />
</section>
</div>
<div class:hidden={isAudioAllowed}>
<button type="button" class="nes-btn" on:click={tryPlay}>{$LL.audio.manager.allow()}</button>
</div>
</div>
<style lang="scss">
div.main-audio-manager.nes-container.is-rounded {
.hidden {
display: none;
}
div.main-audio-manager {
position: absolute;
top: 1%;
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen

View File

@ -10,12 +10,14 @@
import layoutPresentationImg from "./images/layout-presentation.svg";
import layoutChatImg from "./images/layout-chat.svg";
import followImg from "./images/follow.svg";
import lockImg from "./images/lock.svg";
import { LayoutMode } from "../WebRtc/LayoutManager";
import { peerStore } from "../Stores/PeerStore";
import { onDestroy } from "svelte";
import { embedScreenLayout } from "../Stores/EmbedScreensStore";
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { currentPlayerGroupLockStateStore } from "../Stores/CurrentPlayerGroupStore";
const gameScene = gameManager.getCurrentGameScene();
@ -70,6 +72,10 @@
}
}
function lockClick() {
gameScene.connection?.emitLockGroup(!$currentPlayerGroupLockStateStore);
}
let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
isSilent = value;
@ -95,6 +101,15 @@
<img class="noselect" src={followImg} alt="" />
</div>
<div
class="btn-lock"
class:hide={$peerStore.size === 0 || isSilent}
class:disabled={$currentPlayerGroupLockStateStore}
on:click={lockClick}
>
<img class="noselect" src={lockImg} alt="" />
</div>
<div
class="btn-monitor"
on:click={screenSharingClick}
@ -162,7 +177,7 @@
transform: translateY(15px);
transition-timing-function: ease-in-out;
transition: all 0.3s;
margin: 0 4%;
margin: 0 2%;
&.hide {
transform: translateY(60px);
@ -211,6 +226,14 @@
}
}
.btn-lock {
pointer-events: auto;
img {
filter: brightness(0) invert(1);
}
}
@media (hover: none) {
/**
* If we cannot hover over elements, let's display camera button in full.

View File

@ -9,6 +9,8 @@
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import uploadFile from "../images/jitsi.png";
export let index: number;
export let coWebsite: CoWebsite;
export let vertical: boolean;
@ -21,7 +23,7 @@
onMount(() => {
icon.src = isJitsi
? "/resources/logos/jitsi.png"
? uploadFile
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
icon.alt = coWebsite.getUrl().hostname;
icon.onload = () => {
@ -350,9 +352,14 @@
color: white;
padding: 4px;
border-radius: 4px;
p {
margin-bottom: 0;
}
&.hide {
display: none;
}
}
}
</style>

View File

@ -18,5 +18,7 @@
display: flex;
padding-top: 2%;
height: 100%;
position: relative;
z-index: 200;
}
</style>

View File

@ -1,8 +1,6 @@
<script lang="ts">
import { fly } from "svelte/transition";
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
import { getNavigatorType, isAndroid as isAndroidFct, NavigatorType } from "../../WebRtc/DeviceUtils";
import LL from "../../i18n/i18n-svelte";
@ -33,9 +31,9 @@
<p class="err">
{$LL.camera.help.firefoxContent()}
</p>
<img src={firefoxImg} alt="" />
<img src={$LL.camera.help.screen.firefox()} alt="" />
{:else if isChrome && !isAndroid}
<img src={chromeImg} alt="" />
<img src={$LL.camera.help.screen.chrome()} alt="" />
{/if}
</p>
</section>

View File

@ -3,6 +3,7 @@
import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene";
import { DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
import logoImg from "../images/logo.png";
import poweredByWorkAdventureImg from "../images/Powered_By_WorkAdventure_Big.png";
import { gameManager } from "../../Phaser/Game/GameManager";
import LL from "../../i18n/i18n-svelte";
@ -13,6 +14,8 @@
let name = gameManager.getPlayerName() || "";
let startValidating = false;
let logo = gameManager.currentStartedRoom.loginSceneLogo ?? logoImg;
function submit() {
startValidating = true;
@ -25,7 +28,7 @@
<form class="loginScene" on:submit|preventDefault={submit}>
<section class="text-center">
<img src={logoImg} alt="WorkAdventure logo" />
<img src={logo} alt="" />
</section>
<section class="text-center">
<h2>{$LL.login.input.name.placeholder()}</h2>
@ -60,6 +63,11 @@
<section class="action">
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">{$LL.login.continue()}</button>
</section>
{#if logo !== logoImg}
<section class="text-center powered-by">
<img src={poweredByWorkAdventureImg} alt="Powered by WorkAdventure" />
</section>
{/if}
</form>
<style lang="scss">
@ -132,6 +140,11 @@
width: 100%;
margin: 20px 0;
}
&.powered-by {
position: fixed;
bottom: 0;
}
}
}
</style>

View File

@ -54,6 +54,7 @@
});
</script>
<!-- Components ordered by z-index -->
<div id="main-layout" bind:this={mainLayout}>
<aside id="main-layout-left-aside">
{#if $menuIconVisiblilityStore}
@ -104,14 +105,14 @@
<ShareLinkMapModal />
{/if}
{#if $followStateStore !== "off" || $peerStore.size > 0}
<FollowMenu />
{/if}
{#if $actionsMenuStore}
<ActionsMenu />
{/if}
{#if $followStateStore !== "off" || $peerStore.size > 0}
<FollowMenu />
{/if}
{#if $requestVisitCardsStore}
<VisitCard visitCardUrl={$requestVisitCardsStore} />
{/if}

View File

@ -44,7 +44,6 @@
async function logOut() {
disableMenuStores();
loginSceneVisibleStore.set(true);
return connectionManager.logout();
}

View File

@ -7,17 +7,24 @@
import type { Locales } from "../../i18n/i18n-types";
import { displayableLocales, setCurrentLocale } from "../../i18n/locales";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { audioManagerVolumeStore } from "../../Stores/AudioManagerStore";
let fullscreen: boolean = localUserStore.getFullscreen();
let notification: boolean = localUserStore.getNotification() === "granted";
let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger();
let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests();
let decreaseAudioPlayerVolumeWhileTalking: boolean = localUserStore.getDecreaseAudioPlayerVolumeWhileTalking();
let valueGame: number = localUserStore.getGameQualityValue();
let valueVideo: number = localUserStore.getVideoQualityValue();
let valueLocale: string = $locale;
let valueCameraPrivacySettings = localUserStore.getCameraPrivacySettings();
let valueMicrophonePrivacySettings = localUserStore.getMicrophonePrivacySettings();
let previewValueGame = valueGame;
let previewValueVideo = valueVideo;
let previewValueLocale = valueLocale;
let previewCameraPrivacySettings = valueCameraPrivacySettings;
let previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
function saveSetting() {
let change = false;
@ -38,6 +45,18 @@
change = true;
}
if (valueCameraPrivacySettings !== previewCameraPrivacySettings) {
previewCameraPrivacySettings = valueCameraPrivacySettings;
localUserStore.setCameraPrivacySettings(valueCameraPrivacySettings);
}
if (valueMicrophonePrivacySettings !== previewMicrophonePrivacySettings) {
previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
localUserStore.setMicrophonePrivacySettings(valueMicrophonePrivacySettings);
}
audioManagerVolumeStore.setDecreaseWhileTalking(decreaseAudioPlayerVolumeWhileTalking);
if (change) {
window.location.reload();
}
@ -82,6 +101,10 @@
localUserStore.setIgnoreFollowRequests(ignoreFollowRequests);
}
function changeDecreaseAudioPlayerVolumeWhileTalking() {
localUserStore.setDecreaseAudioPlayerVolumeWhileTalking(decreaseAudioPlayerVolumeWhileTalking);
}
function closeMenu() {
menuVisiblilityStore.set(false);
}
@ -154,6 +177,19 @@
</select>
</div>
</section>
<section>
<h3>{$LL.menu.settings.privacySettings.title()}</h3>
<p>{$LL.menu.settings.privacySettings.explanation()}</p>
<label>
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={valueCameraPrivacySettings} />
<span>{$LL.menu.settings.privacySettings.cameraToggle()}</span>
</label>
<label>
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={valueMicrophonePrivacySettings} />
<span>{$LL.menu.settings.privacySettings.microphoneToggle()}</span>
</label>
</section>
<section class="settings-section-save">
<p>{$LL.menu.settings.save.warning()}</p>
<button type="button" class="nes-btn is-primary" on:click|preventDefault={saveSetting}
@ -196,6 +232,15 @@
on:change={changeIgnoreFollowRequests}
/>
<span>{$LL.menu.settings.ignoreFollowRequest()}</span>
<label>
<input
type="checkbox"
class="nes-checkbox is-dark"
bind:checked={decreaseAudioPlayerVolumeWhileTalking}
on:change={changeDecreaseAudioPlayerVolumeWhileTalking}
/>
<span>{$LL.audio.manager.reduce()}</span>
</label>
</label>
</section>
</div>
@ -217,12 +262,15 @@
outline: none;
}
}
section.settings-section-save {
text-align: center;
p {
margin: 16px 0;
}
}
section.settings-section-noSaveOption {
display: flex;
align-items: center;

View File

@ -76,6 +76,7 @@
transform: translate(-50%, 0);
margin-top: 200px;
max-width: 80vw;
z-index: 350;
iframe {
border: 0;

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1 @@
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 25 3 C 18.363281 3 13 8.363281 13 15 L 13 20 L 9 20 C 7.355469 20 6 21.355469 6 23 L 6 47 C 6 48.644531 7.355469 50 9 50 L 41 50 C 42.644531 50 44 48.644531 44 47 L 44 23 C 44 21.355469 42.644531 20 41 20 L 37 20 L 37 15 C 37 8.363281 31.636719 3 25 3 Z M 25 5 C 30.566406 5 35 9.433594 35 15 L 35 20 L 15 20 L 15 15 C 15 9.433594 19.433594 5 25 5 Z M 9 22 L 41 22 C 41.554688 22 42 22.445313 42 23 L 42 47 C 42 47.554688 41.554688 48 41 48 L 9 48 C 8.445313 48 8 47.554688 8 47 L 8 23 C 8 22.445313 8.445313 22 9 22 Z M 25 30 C 23.300781 30 22 31.300781 22 33 C 22 33.898438 22.398438 34.6875 23 35.1875 L 23 38 C 23 39.101563 23.898438 40 25 40 C 26.101563 40 27 39.101563 27 38 L 27 35.1875 C 27.601563 34.6875 28 33.898438 28 33 C 28 31.300781 26.699219 30 25 30 Z"/></svg>

After

Width:  |  Height:  |  Size: 891 B

View File

@ -45,8 +45,10 @@ class ConnectionManager {
/**
* TODO fix me to be move in game manager
*
* Returns the URL that we need to redirect to to load the OpenID screen, or "null" if no redirection needs to happen.
*/
public loadOpenIDScreen() {
public loadOpenIDScreen(): URL | null {
const state = localUserStore.generateState();
const nonce = localUserStore.generateNonce();
localUserStore.setAuthToken(null);
@ -55,11 +57,10 @@ class ConnectionManager {
loginSceneVisibleIframeStore.set(false);
return null;
}
const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`);
const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`, window.location.href);
redirectUrl.searchParams.append("state", state);
redirectUrl.searchParams.append("nonce", nonce);
redirectUrl.searchParams.append("playUri", this._currentRoom.key);
window.location.assign(redirectUrl.toString());
return redirectUrl;
}
@ -83,8 +84,10 @@ class ConnectionManager {
/**
* Tries to login to the node server and return the starting map url to be loaded
*
* @return returns a promise to the Room we are going to load OR a pointer to the URL we must redirect to if authentication is needed.
*/
public async initGameConnexion(): Promise<Room> {
public async initGameConnexion(): Promise<Room | URL> {
this.connexionType = urlManager.getGameConnexionType();
this._currentRoom = null;
@ -100,8 +103,9 @@ class ConnectionManager {
if (this.connexionType === GameConnexionTypes.login) {
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
if (this.loadOpenIDScreen() !== null) {
return Promise.reject(new Error("You will be redirect on login page"));
const redirect = this.loadOpenIDScreen();
if (redirect !== null) {
return redirect;
}
urlManager.pushRoomIdToUrl(this._currentRoom);
} else if (this.connexionType === GameConnexionTypes.jwt) {
@ -124,8 +128,11 @@ class ConnectionManager {
analyticsClient.loggedWithSso();
} catch (err) {
console.error(err);
this.loadOpenIDScreen();
return Promise.reject(new Error("You will be redirect on login page"));
const redirect = this.loadOpenIDScreen();
if (redirect === null) {
throw new Error("Unable to redirect on login page.");
}
return redirect;
}
urlManager.pushRoomIdToUrl(this._currentRoom);
}
@ -213,8 +220,11 @@ class ConnectionManager {
err.response?.data &&
err.response.data !== "User cannot to be connected on openid provider")
) {
this.loadOpenIDScreen();
return Promise.reject(new Error("You will be redirect on login page"));
const redirect = this.loadOpenIDScreen();
if (redirect === null) {
throw new Error("Unable to redirect on login page.");
}
return redirect;
}
}
}

View File

@ -44,6 +44,12 @@ export interface GroupCreatedUpdatedMessageInterface {
position: PositionInterface;
groupId: number;
groupSize: number;
locked: boolean;
}
export interface GroupUsersUpdateMessageInterface {
groupId: number;
userIds: number[];
}
export interface WebRtcDisconnectMessageInterface {

View File

@ -15,6 +15,7 @@ const helpCameraSettingsShown = "helpCameraSettingsShown";
const fullscreenKey = "fullscreen";
const forceCowebsiteTriggerKey = "forceCowebsiteTrigger";
const ignoreFollowRequests = "ignoreFollowRequests";
const decreaseAudioPlayerVolumeWhileTalking = "decreaseAudioPlayerVolumeWhileTalking";
const lastRoomUrl = "lastRoomUrl";
const authToken = "authToken";
const state = "state";
@ -24,11 +25,14 @@ const code = "code";
const cameraSetup = "cameraSetup";
const cacheAPIIndex = "workavdenture-cache";
const userProperties = "user-properties";
const cameraPrivacySettings = "cameraPrivacySettings";
const microphonePrivacySettings = "microphonePrivacySettings";
class LocalUserStore {
saveUser(localUser: LocalUser) {
localStorage.setItem("localUser", JSON.stringify(localUser));
}
getLocalUser(): LocalUser | null {
const data = localStorage.getItem("localUser");
return data ? JSON.parse(data) : null;
@ -37,6 +41,7 @@ class LocalUserStore {
setName(name: string): void {
localStorage.setItem(playerNameKey, name);
}
getName(): string | null {
const value = localStorage.getItem(playerNameKey) || "";
return isUserNameValid(value) ? value : null;
@ -45,6 +50,7 @@ class LocalUserStore {
setPlayerCharacterIndex(playerCharacterIndex: number): void {
localStorage.setItem(selectedPlayerKey, "" + playerCharacterIndex);
}
getPlayerCharacterIndex(): number {
return parseInt(localStorage.getItem(selectedPlayerKey) || "");
}
@ -52,6 +58,7 @@ class LocalUserStore {
setCustomCursorPosition(activeRow: number, selectedLayers: number[]): void {
localStorage.setItem(customCursorPositionKey, JSON.stringify({ activeRow, selectedLayers }));
}
getCustomCursorPosition(): { activeRow: number; selectedLayers: number[] } | null {
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
}
@ -59,6 +66,7 @@ class LocalUserStore {
setCharacterLayers(layers: string[]): void {
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
}
getCharacterLayers(): string[] | null {
const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null");
return areCharacterLayersValid(value) ? value : null;
@ -67,6 +75,7 @@ class LocalUserStore {
setCompanion(companion: string | null): void {
return localStorage.setItem(companionKey, JSON.stringify(companion));
}
getCompanion(): string | null {
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
@ -76,6 +85,7 @@ class LocalUserStore {
return companion;
}
wasCompanionSet(): boolean {
return localStorage.getItem(companionKey) ? true : false;
}
@ -83,6 +93,7 @@ class LocalUserStore {
setGameQualityValue(value: number): void {
localStorage.setItem(gameQualityKey, "" + value);
}
getGameQualityValue(): number {
return parseInt(localStorage.getItem(gameQualityKey) || "60");
}
@ -90,6 +101,7 @@ class LocalUserStore {
setVideoQualityValue(value: number): void {
localStorage.setItem(videoQualityKey, "" + value);
}
getVideoQualityValue(): number {
return parseInt(localStorage.getItem(videoQualityKey) || "20");
}
@ -97,6 +109,7 @@ class LocalUserStore {
setAudioPlayerVolume(value: number): void {
localStorage.setItem(audioPlayerVolumeKey, "" + value);
}
getAudioPlayerVolume(): number {
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || "1");
}
@ -104,6 +117,7 @@ class LocalUserStore {
setAudioPlayerMuted(value: boolean): void {
localStorage.setItem(audioPlayerMuteKey, value.toString());
}
getAudioPlayerMuted(): boolean {
return localStorage.getItem(audioPlayerMuteKey) === "true";
}
@ -111,6 +125,7 @@ class LocalUserStore {
setHelpCameraSettingsShown(): void {
localStorage.setItem(helpCameraSettingsShown, "1");
}
getHelpCameraSettingsShown(): boolean {
return localStorage.getItem(helpCameraSettingsShown) === "1";
}
@ -118,6 +133,7 @@ class LocalUserStore {
setFullscreen(value: boolean): void {
localStorage.setItem(fullscreenKey, value.toString());
}
getFullscreen(): boolean {
return localStorage.getItem(fullscreenKey) === "true";
}
@ -125,6 +141,7 @@ class LocalUserStore {
setForceCowebsiteTrigger(value: boolean): void {
localStorage.setItem(forceCowebsiteTriggerKey, value.toString());
}
getForceCowebsiteTrigger(): boolean {
return localStorage.getItem(forceCowebsiteTriggerKey) === "true";
}
@ -132,9 +149,16 @@ class LocalUserStore {
setIgnoreFollowRequests(value: boolean): void {
localStorage.setItem(ignoreFollowRequests, value.toString());
}
getIgnoreFollowRequests(): boolean {
return localStorage.getItem(ignoreFollowRequests) === "true";
}
setDecreaseAudioPlayerVolumeWhileTalking(value: boolean): void {
localStorage.setItem(decreaseAudioPlayerVolumeWhileTalking, value.toString());
}
getDecreaseAudioPlayerVolumeWhileTalking(): boolean {
return localStorage.getItem(decreaseAudioPlayerVolumeWhileTalking) === "true";
}
async setLastRoomUrl(roomUrl: string): Promise<void> {
localStorage.setItem(lastRoomUrl, roomUrl.toString());
@ -148,11 +172,13 @@ class LocalUserStore {
}
}
}
getLastRoomUrl(): string {
return (
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
);
}
getLastRoomUrlCacheApi(): Promise<string | undefined> {
if (!("caches" in window)) {
return Promise.resolve(undefined);
@ -169,6 +195,7 @@ class LocalUserStore {
setAuthToken(value: string | null) {
value ? localStorage.setItem(authToken, value) : localStorage.removeItem(authToken);
}
getAuthToken(): string | null {
return localStorage.getItem(authToken);
}
@ -195,23 +222,29 @@ class LocalUserStore {
}
return oldValue === value;
}
setState(value: string) {
localStorage.setItem(state, value);
}
getState(): string | null {
return localStorage.getItem(state);
}
generateNonce(): string {
const newNonce = uuidv4();
localStorage.setItem(nonce, newNonce);
return newNonce;
}
getNonce(): string | null {
return localStorage.getItem(nonce);
}
setCode(value: string): void {
localStorage.setItem(code, value);
}
getCode(): string | null {
return localStorage.getItem(code);
}
@ -219,11 +252,36 @@ class LocalUserStore {
setCameraSetup(cameraId: string) {
localStorage.setItem(cameraSetup, cameraId);
}
getCameraSetup(): { video: unknown; audio: unknown } | undefined {
const cameraSetupValues = localStorage.getItem(cameraSetup);
return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined;
}
setCameraPrivacySettings(option: boolean) {
localStorage.setItem(cameraPrivacySettings, option.toString());
}
getCameraPrivacySettings() {
//if this setting doesn't exist in LocalUserStore, we set a default value
if (localStorage.getItem(cameraPrivacySettings) == null) {
localStorage.setItem(cameraPrivacySettings, "false");
}
return localStorage.getItem(cameraPrivacySettings) === "true";
}
setMicrophonePrivacySettings(option: boolean) {
localStorage.setItem(microphonePrivacySettings, option.toString());
}
getMicrophonePrivacySettings() {
//if this setting doesn't exist in LocalUserStore, we set a default value
if (localStorage.getItem(microphonePrivacySettings) == null) {
localStorage.setItem(microphonePrivacySettings, "true");
}
return localStorage.getItem(microphonePrivacySettings) === "true";
}
getAllUserProperties(): Map<string, unknown> {
const result = new Map<string, string>();
for (let i = 0; i < localStorage.length; i++) {

View File

@ -31,6 +31,8 @@ export class Room {
private _group: string | null = null;
private _expireOn: Date | undefined;
private _canReport: boolean = false;
private _loadingLogo: string | undefined;
private _loginSceneLogo: string | undefined;
private constructor(private roomUrl: URL) {
this.id = roomUrl.pathname;
@ -126,6 +128,8 @@ export class Room {
this._expireOn = new Date(data.expireOn);
}
this._canReport = data.canReport ?? false;
this._loadingLogo = data.loadingLogo ?? undefined;
this._loginSceneLogo = data.loginSceneLogo ?? undefined;
return new MapDetail(data.mapUrl);
} else {
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
@ -233,4 +237,12 @@ export class Room {
get canReport(): boolean {
return this._canReport;
}
get loadingLogo(): string | undefined {
return this._loadingLogo;
}
get loginSceneLogo(): string | undefined {
return this._loginSceneLogo;
}
}

View File

@ -5,47 +5,35 @@ import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
import type {
GroupCreatedUpdatedMessageInterface,
ItemEventMessageInterface,
GroupUsersUpdateMessageInterface,
MessageUserJoined,
OnConnectInterface,
PlayerDetailsUpdatedMessageInterface,
PlayGlobalMessageInterface,
PositionInterface,
RoomJoinedMessageInterface,
ViewportInterface,
WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import { adminMessagesService } from "./AdminMessagesService";
import { connectionManager } from "./ConnectionManager";
import { get } from "svelte/store";
import { followRoleStore, followUsersStore } from "../Stores/FollowStore";
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore";
import { localUserStore } from "./LocalUserStore";
import {
RefreshRoomMessage,
ServerToClientMessage as ServerToClientMessageTsProto,
TokenExpiredMessage,
WorldConnexionMessage,
WorldFullMessage,
ErrorMessage as ErrorMessageTsProto,
UserMovedMessage as UserMovedMessageTsProto,
GroupUpdateMessage as GroupUpdateMessageTsProto,
GroupDeleteMessage as GroupDeleteMessageTsProto,
UserJoinedMessage as UserJoinedMessageTsProto,
UserLeftMessage as UserLeftMessageTsProto,
ItemEventMessage as ItemEventMessageTsProto,
EmoteEventMessage as EmoteEventMessageTsProto,
VariableMessage as VariableMessageTsProto,
PlayerDetailsUpdatedMessage as PlayerDetailsUpdatedMessageTsProto,
WorldFullWarningMessage,
WebRtcDisconnectMessage as WebRtcDisconnectMessageTsProto,
PlayGlobalMessage as PlayGlobalMessageTsProto,
StopGlobalMessage as StopGlobalMessageTsProto,
SendJitsiJwtMessage as SendJitsiJwtMessageTsProto,
SendUserMessage as SendUserMessageTsProto,
BanUserMessage as BanUserMessageTsProto,
ClientToServerMessage as ClientToServerMessageTsProto,
PositionMessage as PositionMessageTsProto,
ViewportMessage as ViewportMessageTsProto,
@ -55,8 +43,6 @@ import {
CharacterLayerMessage,
} from "../Messages/ts-proto-generated/messages";
import { Subject } from "rxjs";
import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent";
import { match } from "assert";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
@ -116,6 +102,9 @@ export class RoomConnection implements RoomConnection {
private readonly _groupUpdateMessageStream = new Subject<GroupCreatedUpdatedMessageInterface>();
public readonly groupUpdateMessageStream = this._groupUpdateMessageStream.asObservable();
private readonly _groupUsersUpdateMessageStream = new Subject<GroupUsersUpdateMessageInterface>();
public readonly groupUsersUpdateMessageStream = this._groupUsersUpdateMessageStream.asObservable();
private readonly _groupDeleteMessageStream = new Subject<GroupDeleteMessageTsProto>();
public readonly groupDeleteMessageStream = this._groupDeleteMessageStream.asObservable();
@ -220,6 +209,7 @@ export class RoomConnection implements RoomConnection {
this.socket.onmessage = (messageEvent) => {
const arrayBuffer: ArrayBuffer = messageEvent.data;
const initCharacterLayers = characterLayers;
const serverToClientMessage = ServerToClientMessageTsProto.decode(new Uint8Array(arrayBuffer));
//const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
@ -342,14 +332,16 @@ export class RoomConnection implements RoomConnection {
this._userRoomToken = roomJoinedMessage.userRoomToken;
// If one of the URLs sent to us does not exist, let's go to the Woka selection screen.
for (const characterLayer of roomJoinedMessage.characterLayer) {
if (!characterLayer.url) {
this.goToSelectYourWokaScene();
this.closed = true;
break;
}
if (
roomJoinedMessage.characterLayer.length !== initCharacterLayers.length ||
roomJoinedMessage.characterLayer.find((layer) => !layer.url)
) {
this.goToSelectYourWokaScene();
this.closed = true;
}
if (this.closed) {
this.closeConnection();
break;
}
@ -440,6 +432,10 @@ export class RoomConnection implements RoomConnection {
this._sendJitsiJwtMessageStream.next(message.sendJitsiJwtMessage);
break;
}
case "groupUsersUpdateMessage": {
this._groupUsersUpdateMessageStream.next(message.groupUsersUpdateMessage);
break;
}
case "sendUserMessage": {
adminMessagesService.onSendusermessage(message.sendUserMessage);
break;
@ -672,6 +668,7 @@ export class RoomConnection implements RoomConnection {
groupId: message.groupId,
position: position,
groupSize: message.groupSize,
locked: message.locked,
};
}
@ -887,6 +884,19 @@ export class RoomConnection implements RoomConnection {
this.socket.send(bytes);
}
public emitLockGroup(lock: boolean = true): void {
const bytes = ClientToServerMessageTsProto.encode({
message: {
$case: "lockGroupPromptMessage",
lockGroupPromptMessage: {
lock,
},
},
}).finish();
this.socket.send(bytes);
}
public getAllTags(): string[] {
return this.tags;
}

View File

@ -1,10 +1,12 @@
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
import { DirtyScene } from "../Game/DirtyScene";
import { gameManager } from "../Game/GameManager";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import CancelablePromise from "cancelable-promise";
import Image = Phaser.GameObjects.Image;
import Texture = Phaser.Textures.Texture;
const LogoNameIndex: string = "logoLoading";
const TextName: string = "Loading...";
const LogoResource: string = "static/images/logo.png";
const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 };
const loadingBarHeight: number = 16;
const padding: number = 5;
@ -14,9 +16,15 @@ export class Loader {
private progress!: Phaser.GameObjects.Graphics;
private progressAmount: number = 0;
private logo: Phaser.GameObjects.Image | undefined;
private logoPoweredBy: Phaser.GameObjects.Image | undefined;
private poweredByLogo: Phaser.GameObjects.Image | undefined;
private loadingText: Phaser.GameObjects.Text | null = null;
private logoNameIndex!: string;
private superLoad: SuperLoaderPlugin;
public constructor(private scene: Phaser.Scene) {}
public constructor(private scene: Phaser.Scene) {
this.superLoad = new SuperLoaderPlugin(scene);
}
public addLoader(): void {
// If there is nothing to load, do not display the loader.
@ -24,43 +32,53 @@ export class Loader {
return;
}
const logoResource = gameManager.currentStartedRoom.loadingLogo ?? "static/images/logo.png";
this.logoNameIndex = "logoLoading" + logoResource;
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
return res(
(this.logo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 150,
LogoNameIndex
))
);
} else {
//add loading if logo image is not ready
this.loadingText = this.scene.add.text(
//add loading if logo image until logo image is ready
this.loadingText = this.scene.add.text(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 50,
TextName
);
const logoPromise = this.superLoad.image(this.logoNameIndex, logoResource);
logoPromise
.then((texture) => {
this.logo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 50,
TextName
this.scene.game.renderer.height / 2 - 150,
texture
);
}
this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
this.scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
if (this.loadingText) {
this.loadingText.destroy();
}
return res(
(this.logo = this.scene.add.image(
this.loadingText?.destroy();
})
.catch((e) => console.warn("Could not load logo: ", logoResource, e));
let poweredByLogoPromise: CancelablePromise<Texture> | undefined;
if (gameManager.currentStartedRoom.loadingLogo) {
poweredByLogoPromise = this.superLoad.image(
"poweredByLogo",
"static/images/Powered_By_WorkAdventure_Small.png"
);
poweredByLogoPromise
.then((texture) => {
this.poweredByLogo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 150,
LogoNameIndex
))
this.scene.game.renderer.height - 50,
texture
);
})
.catch((e) =>
console.warn('Could not load image "static/images/Powered_By_WorkAdventure_Small.png"', e)
);
});
});
}
this.progressContainer = this.scene.add.graphics();
this.progress = this.scene.add.graphics();
this.progressContainer.fillStyle(0x444444, 0.8);
this.progress = this.scene.add.graphics();
this.resize();
@ -68,26 +86,35 @@ export class Loader {
this.progressAmount = value;
this.drawProgress();
});
const resizeFunction = this.resize.bind(this);
this.scene.scale.on(Phaser.Scale.Events.RESIZE, resizeFunction);
this.scene.load.on("complete", () => {
if (this.loadingText) {
this.loadingText.destroy();
}
promiseLoadLogoTexture
.then((resLoadingImage: Phaser.GameObjects.Image) => {
resLoadingImage.destroy();
})
.catch((e) => console.error(e));
logoPromise.cancel();
poweredByLogoPromise?.cancel();
this.logo?.destroy();
this.poweredByLogo?.destroy();
this.progress.destroy();
this.progressContainer.destroy();
if (this.scene instanceof DirtyScene) {
this.scene.markDirty();
}
this.scene.scale.off(Phaser.Scale.Events.RESIZE, resizeFunction);
});
}
public removeLoader(): void {
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
this.scene.load.textureManager.remove(LogoNameIndex);
if (this.scene.load.textureManager.exists(this.logoNameIndex)) {
this.scene.load.textureManager.remove(this.logoNameIndex);
}
if (this.scene.load.textureManager.exists("poweredByLogo")) {
this.scene.load.textureManager.remove("poweredByLogo");
}
}
@ -114,6 +141,11 @@ export class Loader {
this.logo.x = this.scene.game.renderer.width / 2;
this.logo.y = this.scene.game.renderer.height / 2 - 150;
}
if (this.poweredByLogo) {
this.poweredByLogo.x = this.scene.game.renderer.width / 2;
this.poweredByLogo.y = this.scene.game.renderer.height - 40;
}
}
private drawProgress() {

View File

@ -57,8 +57,8 @@ export class SoundMeter {
this.context = context;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.analyser.fftSize = 256;
const bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(bufferLength);
}

View File

@ -18,6 +18,7 @@ import { createColorStore } from "../../Stores/OutlineColorStore";
import type { OutlineableInterface } from "../Game/OutlineableInterface";
import type CancelablePromise from "cancelable-promise";
import { TalkIcon } from "../Components/TalkIcon";
import { Deferred } from "ts-deferred";
const playerNameY = -25;
@ -50,6 +51,11 @@ export abstract class Character extends Container implements OutlineableInterfac
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
private texturePromise: CancelablePromise<string[] | void> | undefined;
/**
* A deferred promise that resolves when the texture of the character is actually displayed.
*/
private textureLoadedDeferred = new Deferred<void>();
constructor(
scene: GameScene,
x: number,
@ -78,12 +84,13 @@ export abstract class Character extends Container implements OutlineableInterfac
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
this.textureLoadedDeferred.resolve();
return this.getSnapshot().then((htmlImageElementSrc) => {
this._pictureStore.set(htmlImageElementSrc);
});
})
.catch(() => {
return lazyLoadPlayerCharacterTextures(scene.load, [
return lazyLoadPlayerCharacterTextures(scene.superLoad, [
{
id: "color_22",
img: "resources/customisation/character_color/character_color21.png",
@ -92,11 +99,20 @@ export abstract class Character extends Container implements OutlineableInterfac
id: "eyes_23",
img: "resources/customisation/character_eyes/character_eyes23.png",
},
]).then((textures) => {
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
});
])
.then((textures) => {
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
this.textureLoadedDeferred.resolve();
return this.getSnapshot().then((htmlImageElementSrc) => {
this._pictureStore.set(htmlImageElementSrc);
});
})
.catch((e) => {
this.textureLoadedDeferred.reject(e);
throw e;
});
})
.finally(() => {
this.texturePromise = undefined;
@ -517,4 +533,13 @@ export abstract class Character extends Container implements OutlineableInterfac
public characterFarAwayOutline(): void {
this.outlineColorStore.characterFarAway();
}
/**
* Returns a promise that resolves as soon as a texture is displayed for the user.
* The promise will return when the required texture is loaded OR when the fallback texture is loaded (in case
* the required texture could not be loaded).
*/
public getTextureLoadedPromise(): PromiseLike<void> {
return this.textureLoadedDeferred.promise;
}
}

View File

@ -1,5 +1,7 @@
//The list of all the player textures, both the default models and the partial textures used for customization
import { WokaList, WokaPartType } from "../../Messages/JsonMessages/PlayerTextures";
export interface BodyResourceDescriptionListInterface {
[key: string]: BodyResourceDescriptionInterface;
}
@ -33,24 +35,6 @@ export enum PlayerTexturesKey {
Woka = "woka",
}
type PlayerTexturesMetadata = Record<PlayerTexturesKey, PlayerTexturesCategory>;
interface PlayerTexturesCategory {
collections: PlayerTexturesCollection[];
required?: boolean;
}
interface PlayerTexturesCollection {
name: string;
textures: PlayerTexturesRecord[];
}
interface PlayerTexturesRecord {
id: string;
name: string;
url: string;
}
export class PlayerTextures {
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
@ -61,7 +45,7 @@ export class PlayerTextures {
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
private LAYERS: BodyResourceDescriptionListInterface[] = [];
public loadPlayerTexturesMetadata(metadata: PlayerTexturesMetadata): void {
public loadPlayerTexturesMetadata(metadata: WokaList): void {
this.mapTexturesMetadataIntoResources(metadata);
}
@ -88,7 +72,7 @@ export class PlayerTextures {
return this.LAYERS;
}
private mapTexturesMetadataIntoResources(metadata: PlayerTexturesMetadata): void {
private mapTexturesMetadataIntoResources(metadata: WokaList): void {
this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka);
this.COLOR_RESOURCES = this.getMappedResources(metadata.body);
this.EYES_RESOURCES = this.getMappedResources(metadata.eyes);
@ -107,7 +91,7 @@ export class PlayerTextures {
];
}
private getMappedResources(category: PlayerTexturesCategory): BodyResourceDescriptionListInterface {
private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
const resources: BodyResourceDescriptionListInterface = {};
if (!category) {
return {};

View File

@ -2,6 +2,8 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type { CharacterTexture } from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures";
import CancelablePromise from "cancelable-promise";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import Texture = Phaser.Textures.Texture;
export interface FrameConfig {
frameWidth: number;
@ -35,81 +37,33 @@ export const loadAllDefaultModels = (
};
export const loadWokaTexture = (
loaderPlugin: LoaderPlugin,
superLoaderPlugin: SuperLoaderPlugin,
texture: BodyResourceDescriptionInterface
): CancelablePromise<BodyResourceDescriptionInterface> => {
return createLoadingPromise(loaderPlugin, texture, {
): CancelablePromise<Texture> => {
return superLoaderPlugin.spritesheet(texture.id, texture.img, {
frameWidth: 32,
frameHeight: 32,
});
};
export const lazyLoadPlayerCharacterTextures = (
loadPlugin: LoaderPlugin,
superLoaderPlugin: SuperLoaderPlugin,
textures: BodyResourceDescriptionInterface[]
): CancelablePromise<string[]> => {
const promisesList: CancelablePromise<unknown>[] = [];
textures.forEach((texture) => {
try {
//TODO refactor
if (!loadPlugin.textureManager.exists(texture.id)) {
promisesList.push(
createLoadingPromise(loadPlugin, texture, {
frameWidth: 32,
frameHeight: 32,
})
);
}
} catch (err) {
console.error(err);
}
});
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
returnPromise = CancelablePromise.all(promisesList).then(() => textures);
} else {
returnPromise = CancelablePromise.resolve(textures);
const promisesList: CancelablePromise<Texture>[] = [];
for (const texture of textures) {
promisesList.push(
superLoaderPlugin.spritesheet(texture.id, texture.img, {
frameWidth: 32,
frameHeight: 32,
})
);
}
const returnPromise: CancelablePromise<Texture[]> = CancelablePromise.all(promisesList);
//If the loading fail, we render the default model instead.
return returnPromise.then((keys) =>
keys.map((key) => {
return typeof key !== "string" ? key.id : key;
return returnPromise.then(() =>
textures.map((key) => {
return key.id;
})
);
};
export const createLoadingPromise = (
loadPlugin: LoaderPlugin,
playerResourceDescriptor: BodyResourceDescriptionInterface,
frameConfig: FrameConfig
) => {
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.id)) {
return res(playerResourceDescriptor);
}
cancel(() => {
loadPlugin.off("loaderror");
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id);
return;
});
loadPlugin.spritesheet(playerResourceDescriptor.id, playerResourceDescriptor.img, frameConfig);
const errorCallback = (file: { src: string }) => {
if (file.src !== playerResourceDescriptor.img) return;
console.error("failed loading player resource: ", playerResourceDescriptor);
rej(playerResourceDescriptor);
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
loadPlugin.off("loaderror", errorCallback);
};
const successCallback = () => {
loadPlugin.off("loaderror", errorCallback);
res(playerResourceDescriptor);
};
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
loadPlugin.on("loaderror", errorCallback);
});
};

View File

@ -9,6 +9,7 @@ import { EnableCameraSceneName } from "../Login/EnableCameraScene";
import { LoginSceneName } from "../Login/LoginScene";
import { SelectCharacterSceneName } from "../Login/SelectCharacterScene";
import { GameScene } from "./GameScene";
import { EmptySceneName } from "../Login/EmptyScene";
/**
* This class should be responsible for any scene starting/stopping
@ -19,7 +20,7 @@ export class GameManager {
private companion: string | null;
private startRoom!: Room;
private cameraSetup?: { video: unknown; audio: unknown };
currentGameSceneName: string | null = null;
private currentGameSceneName: string | null = null;
// Note: this scenePlugin is the scenePlugin of the EntryScene. We should always provide a key in methods called on this scenePlugin.
private scenePlugin!: Phaser.Scenes.ScenePlugin;
@ -32,7 +33,14 @@ export class GameManager {
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
this.scenePlugin = scenePlugin;
this.startRoom = await connectionManager.initGameConnexion();
const result = await connectionManager.initGameConnexion();
if (result instanceof URL) {
window.location.assign(result.toString());
// window.location.assign is not immediate and Javascript keeps running after.
// so we need to redirect to an empty Phaser scene, waiting for the redirection to take place
return EmptySceneName;
}
this.startRoom = result;
this.loadMap(this.startRoom);
//If player name was not set show login scene with player name

View File

@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager";
import { SharedVariablesManager } from "./SharedVariablesManager";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { lazyLoadPlayerCharacterTextures, loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadPlayerCharacterTextures } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import { iframeListener } from "../../Api/IframeListener";
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
@ -76,6 +76,7 @@ import { userIsAdminStore } from "../../Stores/GameStore";
import { contactPageStore } from "../../Stores/MenuStore";
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
import { currentPlayerGroupIdStore, currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore";
import EVENT_TYPE = Phaser.Scenes.Events;
import Texture = Phaser.Textures.Texture;
@ -97,8 +98,10 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import type { VideoPeer } from "../../WebRtc/VideoPeer";
import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
export interface GameSceneInitInterface {
initPosition: PointInterface | null;
reconnecting: boolean;
@ -164,13 +167,9 @@ export class GameScene extends DirtyScene {
private playersPositionInterpolator = new PlayersPositionInterpolator();
public connection: RoomConnection | undefined;
private simplePeer!: SimplePeer;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (
value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>
) => void;
private connectionAnswerPromiseDeferred: Deferred<RoomJoinedMessageInterface>;
// A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
private createPromiseDeferred: Deferred<void>;
private iframeSubscriptionList!: Array<Subscription>;
private peerStoreUnsubscribe!: Unsubscriber;
private emoteUnsubscribe!: Unsubscriber;
@ -179,6 +178,7 @@ export class GameScene extends DirtyScene {
private volumeStoreUnsubscribers: Map<number, Unsubscriber> = new Map<number, Unsubscriber>();
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
private followUsersColorStoreUnsubscribe!: Unsubscriber;
private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber;
private biggestAvailableAreaStoreUnsubscribe!: () => void;
MapUrlFile: string;
@ -220,6 +220,8 @@ export class GameScene extends DirtyScene {
private loader: Loader;
private lastCameraEvent: WasCameraUpdatedEvent | undefined;
private firstCameraUpdateSent: boolean = false;
private currentPlayerGroupId?: number;
public readonly superLoad: SuperLoaderPlugin;
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
super({
@ -232,13 +234,10 @@ export class GameScene extends DirtyScene {
this.MapUrlFile = MapUrlFile;
this.roomUrl = room.key;
this.createPromise = new Promise<void>((resolve, reject): void => {
this.createPromiseResolve = resolve;
});
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve;
});
this.createPromiseDeferred = new Deferred<void>();
this.connectionAnswerPromiseDeferred = new Deferred<RoomJoinedMessageInterface>();
this.loader = new Loader(this);
this.superLoad = new SuperLoaderPlugin(this);
}
//hook preload scene
@ -408,11 +407,11 @@ export class GameScene extends DirtyScene {
this.load.on("complete", () => {
// FIXME: the factory might fail because the resources might not be loaded yet...
// We would need to add a loader ended event in addition to the createPromise
this.createPromise
this.createPromiseDeferred.promise
.then(async () => {
itemFactory.create(this);
const roomJoinedAnswer = await this.connectionAnswerPromise;
const roomJoinedAnswer = await this.connectionAnswerPromiseDeferred.promise;
for (const object of objectsOfType) {
// TODO: we should pass here a factory to create sprites (maybe?)
@ -609,7 +608,7 @@ export class GameScene extends DirtyScene {
}
}
this.createPromiseResolve();
this.createPromiseDeferred.resolve();
// Now, let's load the script, if any
const scripts = this.getScriptUrls(this.mapFile);
const disableModuleMode = this.getProperty(this.mapFile, GameMapProperties.SCRIPT_DISABLE_MODULE_SUPPORT) as
@ -635,7 +634,7 @@ export class GameScene extends DirtyScene {
}
const talkIconVolumeTreshold = 10;
let oldPeerNumber = 0;
const oldPeers = new Map<number, VideoPeer>();
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
this.volumeStoreUnsubscribers.forEach((unsubscribe) => unsubscribe());
this.volumeStoreUnsubscribers.clear();
@ -652,10 +651,17 @@ export class GameScene extends DirtyScene {
}
const newPeerNumber = peers.size;
if (newPeerNumber > oldPeerNumber) {
if (newPeerNumber > oldPeers.size) {
this.playSound("audio-webrtc-in");
} else if (newPeerNumber < oldPeerNumber) {
} else if (newPeerNumber < oldPeers.size) {
this.playSound("audio-webrtc-out");
const oldPeersKeys = oldPeers.keys();
const newPeersKeys = Array.from(peers.keys());
for (const oldKey of oldPeersKeys) {
if (!newPeersKeys.includes(oldKey)) {
this.MapPlayersByKey.get(oldKey)?.showTalkIcon(false, true);
}
}
}
if (newPeerNumber > 0) {
if (!this.localVolumeStoreUnsubscriber) {
@ -673,7 +679,10 @@ export class GameScene extends DirtyScene {
this.localVolumeStoreUnsubscriber = undefined;
}
}
oldPeerNumber = newPeerNumber;
oldPeers.clear();
for (const [key, val] of peers) {
oldPeers.set(key, val);
}
});
this.emoteUnsubscribe = emoteStore.subscribe((emote) => {
@ -702,7 +711,15 @@ export class GameScene extends DirtyScene {
}
});
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises])
this.currentPlayerGroupIdStoreUnsubscribe = currentPlayerGroupIdStore.subscribe((groupId) => {
this.currentPlayerGroupId = groupId;
});
Promise.all([
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
...scriptPromises,
this.CurrentPlayer.getTextureLoadedPromise() as Promise<unknown>,
])
.then(() => {
this.scene.wake();
})
@ -739,7 +756,7 @@ export class GameScene extends DirtyScene {
.then((onConnect: OnConnectInterface) => {
this.connection = onConnect.connection;
lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers)
lazyLoadPlayerCharacterTextures(this.superLoad, onConnect.room.characterLayers)
.then((layers) => {
this.currentPlayerTexturesResolve(layers);
})
@ -829,6 +846,11 @@ export class GameScene extends DirtyScene {
});
});
this.connection.groupUsersUpdateMessageStream.subscribe((message) => {
// TODO: how else can we deduce our current group?
currentPlayerGroupIdStore.set(message.groupId);
});
/**
* Triggered when we receive the JWT token to connect to Jitsi
*/
@ -868,7 +890,7 @@ export class GameScene extends DirtyScene {
);
//this.initUsersPosition(roomJoinedMessage.users);
this.connectionAnswerPromiseResolve(onConnect.room);
this.connectionAnswerPromiseDeferred.resolve(onConnect.room);
// Analyze tags to find if we are admin. If yes, show console.
if (this.scene.isSleeping()) {
@ -959,7 +981,9 @@ export class GameScene extends DirtyScene {
context.arc(48, 48, 48, 0, 2 * Math.PI, false);
// context.lineWidth = 5;
context.strokeStyle = "#ffffff";
context.fillStyle = "#ffffff44";
context.stroke();
context.fill();
this.circleTexture.refresh();
//create red circle canvas use to create sprite
@ -969,7 +993,9 @@ export class GameScene extends DirtyScene {
contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false);
//context.lineWidth = 5;
contextRed.strokeStyle = "#ff0000";
contextRed.fillStyle = "#ff000044";
contextRed.stroke();
contextRed.fill();
this.circleRedTexture.refresh();
}
@ -1283,7 +1309,7 @@ ${escapedMessage}
iframeListener.registerAnswerer("getState", async () => {
// The sharedVariablesManager is not instantiated before the connection is established. So we need to wait
// for the connection to send back the answer.
await this.connectionAnswerPromise;
await this.connectionAnswerPromiseDeferred.promise;
return {
mapUrl: this.MapUrlFile,
startLayerName: this.startPositionCalculator.startLayerName,
@ -1306,7 +1332,7 @@ ${escapedMessage}
})
);
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
return this.connectionAnswerPromise.then(() => {
return this.connectionAnswerPromiseDeferred.promise.then(() => {
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
let newFirstgid = 1;
@ -1839,12 +1865,15 @@ ${escapedMessage}
case "GroupCreatedUpdatedEvent":
this.doShareGroupPosition(event.event);
break;
case "DeleteGroupEvent":
this.doDeleteGroup(event.groupId);
break;
case "PlayerDetailsUpdated":
this.doUpdatePlayerDetails(event.details);
break;
case "DeleteGroupEvent": {
this.doDeleteGroup(event.groupId);
currentPlayerGroupIdStore.set(undefined);
currentPlayerGroupLockStateStore.set(undefined);
break;
}
default: {
const tmp: never = event;
}
@ -1907,7 +1936,7 @@ ${escapedMessage}
return;
}
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
const texturesPromise = lazyLoadPlayerCharacterTextures(this.superLoad, addPlayerData.characterLayers);
const player = new RemotePlayer(
addPlayerData.userId,
addPlayerData.userUuid,
@ -2018,11 +2047,16 @@ ${escapedMessage}
this,
Math.round(groupPositionMessage.position.x),
Math.round(groupPositionMessage.position.y),
groupPositionMessage.groupSize === MAX_PER_GROUP ? "circleSprite-red" : "circleSprite-white"
groupPositionMessage.groupSize === MAX_PER_GROUP || groupPositionMessage.locked
? "circleSprite-red"
: "circleSprite-white"
);
sprite.setDisplayOrigin(48, 48);
this.add.existing(sprite);
this.groups.set(groupPositionMessage.groupId, sprite);
if (this.currentPlayerGroupId === groupPositionMessage.groupId) {
currentPlayerGroupLockStateStore.set(groupPositionMessage.locked);
}
return sprite;
}
@ -2078,8 +2112,6 @@ ${escapedMessage}
right: camera.scrollX + camera.width,
bottom: camera.scrollY + camera.height,
});
this.loader.resize();
}
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {

View File

@ -3,39 +3,16 @@ import { BodyResourceDescriptionInterface, PlayerTexturesKey } from "../Entity/P
import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
import type CancelablePromise from "cancelable-promise";
import { PlayerTextures } from "../Entity/PlayerTextures";
import Texture = Phaser.Textures.Texture;
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
export abstract class AbstractCharacterScene extends ResizableScene {
protected playerTextures: PlayerTextures;
protected superLoad: SuperLoaderPlugin;
constructor(params: { key: string }) {
super(params);
this.playerTextures = new PlayerTextures();
}
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.playerTextures.getTexturesResources(PlayerTexturesKey.Woka);
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of Object.values(textures)) {
if (texture.level === -1) {
continue;
}
promises.push(loadWokaTexture(this.load, texture));
}
}
return Promise.all(promises);
}
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
for (const textures of this.playerTextures.getLayers()) {
for (const texture of Object.values(textures)) {
if (texture.level !== -1) {
continue;
}
promises.push(loadWokaTexture(this.load, texture));
}
}
return Promise.all(promises);
this.superLoad = new SuperLoaderPlugin(this);
}
}

View File

@ -16,6 +16,7 @@ import { get } from "svelte/store";
import { analyticsClient } from "../../Administration/AnalyticsClient";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
export const CustomizeSceneName = "CustomizeScene";
@ -41,45 +42,30 @@ export class CustomizeScene extends AbstractCharacterScene {
}
preload() {
const wokaMetadataKey = "woka-list";
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey);
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
this.load.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
this.superLoad
.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
},
withCredentials: true,
},
withCredentials: true,
}
);
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
this.loadCustomSceneSelectCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if (
bodyResourceDescription.level == undefined ||
bodyResourceDescription.level < 0 ||
bodyResourceDescription.level > 5
) {
throw new Error("Texture level is null");
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
(key, type, data) => {
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.layers = loadAllLayers(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
this.loader.addLoader();
});
this.layers = loadAllLayers(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
//this function must stay at the end of preload function
this.loader.addLoader();
}
create() {

View File

@ -0,0 +1,17 @@
import { Scene } from "phaser";
export const EmptySceneName = "EmptyScene";
export class EmptyScene extends Scene {
constructor() {
super({
key: EmptySceneName,
});
}
preload() {}
create() {}
update(time: number, delta: number): void {}
}

View File

@ -28,7 +28,10 @@ export class LoginScene extends ResizableScene {
gameManager.currentStartedRoom &&
gameManager.currentStartedRoom.authenticationMandatory
) {
connectionManager.loadOpenIDScreen();
const redirect = connectionManager.loadOpenIDScreen();
if (redirect !== null) {
window.location.assign(redirect.toString());
}
loginSceneVisibleIframeStore.set(true);
}
loginSceneVisibleStore.set(true);

View File

@ -16,6 +16,7 @@ import { analyticsClient } from "../../Administration/AnalyticsClient";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";
@ -44,38 +45,31 @@ export class SelectCharacterScene extends AbstractCharacterScene {
}
preload() {
const wokaMetadataKey = "woka-list";
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey);
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
this.load.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
this.superLoad
.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
},
withCredentials: true,
},
withCredentials: true,
}
);
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
this.loadSelectSceneCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
(key, type, data) => {
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
//this function must stay at the end of preload function
this.loader.addLoader();
});
//this function must stay at the end of preload function
this.loader.addLoader();
}
create() {

View File

@ -0,0 +1,176 @@
import CancelablePromise from "cancelable-promise";
import { Scene } from "phaser";
import Texture = Phaser.Textures.Texture;
/**
* A wrapper around Phaser LoaderPlugin. Each method returns a (cancelable) Promise that resolves as soon as
* the file is loaded.
*
* As a bonus, if the scene is destroyed BEFORE the promise resolves, the promise is canceled automatically.
* So there is no risk of trying to add a resource on a closed scene.
*/
export class SuperLoaderPlugin {
constructor(private scene: Scene) {}
public spritesheet(
key: string,
url: string,
frameConfig?: Phaser.Types.Loader.FileTypes.ImageFrameConfig,
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject
) {
return this.loadResource<Texture>(
() => {
this.scene.load.spritesheet(key, url, frameConfig, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.textureManager.exists(key)) {
return this.scene.load.textureManager.get(key);
}
return undefined;
},
"spritesheet"
);
}
public image(key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
return this.loadResource<Texture>(
() => {
this.scene.load.image(key, url, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.textureManager.exists(key)) {
return this.scene.load.textureManager.get(key);
}
return undefined;
},
"image"
);
}
/**
* @param key
* @param url
* @param dataKey
* @param xhrSettings
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
*/
public json(
key: string,
url: string,
dataKey?: string,
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject,
immediateCallback?: (key: string, type: string, data: unknown) => void
) {
return this.loadResource<unknown>(
() => {
this.scene.load.json(key, url, dataKey, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.cacheManager.json.exists(key)) {
return this.scene.load.cacheManager.json.get(key);
}
return undefined;
},
"json",
immediateCallback
);
}
/**
* @param callback The function that calls the loader to load a resource
* @param key The key of the resource to be loaded
* @param fromCache A function that checks in the cache if the resource is already available
* @param type The type of resource loaded
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
* @private
*/
private loadResource<T>(
callback: () => void,
key: string,
url: string,
fromCache: () => T | undefined,
type: string,
immediateCallback?: (key: string, type: string, data: unknown) => void
): CancelablePromise<T> {
// If for some reason, the "url" is empty, let's reject the promise.
if (!url) {
console.error("Tried to load an empty " + type + ". URL is missing.");
return CancelablePromise.reject(Error("Failed loading " + type + ": URL is empty"));
}
return new CancelablePromise<T>((res, rej, cancel) => {
if (this.scene.scene.settings.status === Phaser.Scenes.DESTROYED) {
rej(new Error("Trying to load a " + type + " in a Scene that is already destroyed."));
}
const resource = fromCache();
if (resource !== undefined) {
return res(resource);
}
let destroySceneEventRegistered = false;
const unloadCallbacks = () => {
this.scene.load.off("filecomplete-" + type + "-" + key, successCallback);
this.scene.load.off("loaderror", errorCallback);
if (destroySceneEventRegistered) {
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
}
};
const errorCallback = (file: { src: string }) => {
if (file.src !== url) return;
console.error("Failed loading " + type + ": ", url);
rej(new Error('Failed loading "+type+": "' + url + '"'));
unloadCallbacks();
};
const successCallback = (key: string, type: string, data: unknown) => {
this.scene.load.off("loaderror", errorCallback);
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
const resource = fromCache();
if (!resource) {
return rej(new Error("Newly loaded resource not available in cache"));
}
res(resource);
// The "then" callbacks registered on the promise are not executed immediately when "res" is called.
// Instead, they are called after the current JS thread finishes.
// In some cases, we want the callbacks to execute right now. In particular if we load a map / json file
// that contains references to other files that needs to be loaded.
if (immediateCallback) {
immediateCallback(key, type, data);
}
console.log("Resolve done for ", url);
};
cancel(() => {
unloadCallbacks();
});
callback();
this.scene.load.once("filecomplete-" + type + "-" + key, successCallback);
this.scene.load.on("loaderror", errorCallback);
if (this.scene.scene.settings.status > Phaser.Scenes.LOADING) {
// When the scene is destroyed, let's remove our callbacks.
// We only need to register this destroy event is the scene is not in loading state (otherwise, Phaser
// will take care of that for us).
destroySceneEventRegistered = true;
this.scene.events.once(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
// Let's start the loader if we are no more in the scene loading state
this.scene.load.start();
// Due to a bug, if the loader is already started, additional items are not added.... unless we
// explicitly call the "update" method.
this.scene.load.update();
}
});
}
}

View File

@ -0,0 +1,4 @@
import { writable } from "svelte/store";
export const currentPlayerGroupIdStore = writable<number | undefined>(undefined);
export const currentPlayerGroupLockStateStore = writable<boolean | undefined>(undefined);

View File

@ -11,7 +11,7 @@ import { peerStore } from "./PeerStore";
import { privacyShutdownStore } from "./PrivacyShutdownStore";
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
import { SoundMeter } from "../Phaser/Components/SoundMeter";
import { AudioContext } from "standardized-audio-context";
import { visibilityStore } from "./VisibilityStore";
/**
* A store that contains the camera state requested by the user (on or off).
@ -242,6 +242,7 @@ export const mediaStreamConstraintsStore = derived(
privacyShutdownStore,
cameraEnergySavingStore,
isSilentStore,
visibilityStore,
],
(
[
@ -254,6 +255,7 @@ export const mediaStreamConstraintsStore = derived(
$privacyShutdownStore,
$cameraEnergySavingStore,
$isSilentStore,
$visibilityStore,
],
set
) => {
@ -292,7 +294,14 @@ export const mediaStreamConstraintsStore = derived(
// Disable webcam for privacy reasons (the game is not visible and we were talking to no one)
if ($privacyShutdownStore === true) {
currentVideoConstraint = false;
const userMicrophonePrivacySetting = localUserStore.getMicrophonePrivacySettings();
const userCameraPrivacySetting = localUserStore.getCameraPrivacySettings();
if (!userMicrophonePrivacySetting) {
currentAudioConstraint = false;
}
if (!userCameraPrivacySetting) {
currentVideoConstraint = false;
}
}
// Disable webcam for energy reasons (the user is not moving and we are talking to no one)
@ -545,8 +554,12 @@ export const obtainedMediaConstraintStore = derived<Readable<MediaStreamConstrai
export const localVolumeStore = readable<number | undefined>(undefined, (set) => {
let timeout: ReturnType<typeof setTimeout>;
let soundMeter: SoundMeter;
const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => {
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
if (localStreamStoreValue.type === "error") {
set(undefined);
return;
@ -557,7 +570,7 @@ export const localVolumeStore = readable<number | undefined>(undefined, (set) =>
set(undefined);
return;
}
const soundMeter = new SoundMeter(mediaStream);
soundMeter = new SoundMeter(mediaStream);
let error = false;
timeout = setInterval(() => {
@ -575,6 +588,9 @@ export const localVolumeStore = readable<number | undefined>(undefined, (set) =>
return () => {
unsubscribe();
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
};
});

View File

@ -619,7 +619,10 @@ class CoWebsiteManager {
setTimeout(() => {
this.fire();
}, animationTime);
} else if (!highlightedEmbed) {
} else if (
!highlightedEmbed &&
this.getCoWebsites().find((searchCoWebsite) => searchCoWebsite.getId() === coWebsite.getId())
) {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: coWebsite,

View File

@ -197,7 +197,10 @@ class JitsiFactory {
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("videoConferenceJoined", () => {
this.jitsiApi?.executeCommand("displayName", playerName);
});
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);

View File

@ -74,12 +74,17 @@ export class VideoPeer extends Peer {
this.volumeStore = readable<number | undefined>(undefined, (set) => {
let timeout: ReturnType<typeof setTimeout>;
let soundMeter: SoundMeter;
const unsubscribe = this.streamStore.subscribe((mediaStream) => {
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
set(undefined);
return;
}
const soundMeter = new SoundMeter(mediaStream);
soundMeter = new SoundMeter(mediaStream);
let error = false;
timeout = setInterval(() => {
@ -97,6 +102,9 @@ export class VideoPeer extends Peer {
return () => {
unsubscribe();
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
};
});

View File

@ -2,7 +2,8 @@ import type { Translation } from "../i18n-types";
const audio: NonNullable<Translation["audio"]> = {
manager: {
reduce: "Während Unterhaltungen verringern",
reduce: "Verringern Sie die Lautstärke des Audioplayers während des Sprechens",
allow: "Ton zulassen",
},
message: "Sprachnachricht",
};

View File

@ -13,6 +13,10 @@ const camera: NonNullable<Translation["camera"]> = {
'Bitte klicke auf "Diese Entscheidungen speichern" Schaltfläche um erneute Nachfragen nach der Freigabe in Firefox zu verhindern.',
refresh: "Aktualisieren",
continue: "Ohne Kamera fortfahren",
screen: {
firefox: "/resources/help-setting-camera-permission/de-DE-chrome.png",
chrome: "/resources/help-setting-camera-permission/de-DE-chrome.png",
},
},
my: {
silentZone: "Stiller Bereich",

View File

@ -57,6 +57,13 @@ const menu: NonNullable<Translation["menu"]> = {
language: {
title: "Sprache",
},
privacySettings: {
title: "Einstellungen Abwesenheitsmodus",
explanation:
"Falls der WorkAdventure Tab nicht aktiv ist wird in den Abwesenheitsmodus umgeschaltet. Für diesen Modus kann eingestellt werden, ob die Kamera und/oder das Mikrofon deaktiviert sind solange der Tab nicht sichtbar ist.",
cameraToggle: "Kamera",
microphoneToggle: "Mikrofon",
},
save: {
warning: "(Das Spiel wird nach dem Speichern neugestartet)",
button: "Speichern",

View File

@ -2,7 +2,8 @@ import type { BaseTranslation } from "../i18n-types";
const audio: BaseTranslation = {
manager: {
reduce: "reduce in conversations",
reduce: "Decrease audio player volume while speaking",
allow: "Allow audio",
},
message: "Audio message",
};

View File

@ -13,6 +13,10 @@ const camera: BaseTranslation = {
'Please click the "Remember this decision" checkbox, if you don\'t want Firefox to keep asking you the authorization.',
refresh: "Refresh",
continue: "Continue without webcam",
screen: {
firefox: "/resources/help-setting-camera-permission/en-US-firefox.png",
chrome: "/resources/help-setting-camera-permission/en-US-firefox.png",
},
},
my: {
silentZone: "Silent zone",

View File

@ -57,6 +57,13 @@ const menu: BaseTranslation = {
language: {
title: "Language",
},
privacySettings: {
title: "Away mode settings",
explanation:
'When the WorkAdventure tab is not visible, it switches to "away mode". In this mode, you can decide to automatically disable your webcam and/or microphone for as long as the tab stays hidden.',
cameraToggle: "Camera",
microphoneToggle: "Microphone",
},
save: {
warning: "(Saving these settings will restart the game)",
button: "Save",

View File

@ -2,7 +2,8 @@ import type { Translation } from "../i18n-types";
const audio: NonNullable<Translation["audio"]> = {
manager: {
reduce: "réduit dans les conversations",
reduce: "Diminuer le volume du lecteur audio dans les conversations",
allow: "Autoriser l'audio",
},
message: "Message audio",
};

View File

@ -13,6 +13,10 @@ const camera: NonNullable<Translation["camera"]> = {
'Veuillez cocher la case "Se souvenir de cette décision" si vous ne voulez pas que Firefox vous demande sans cesse l\'autorisation.',
refresh: "Rafraîchir",
continue: "Continuer sans webcam",
screen: {
firefox: "/resources/help-setting-camera-permission/fr-FR-chrome.png",
chrome: "/resources/help-setting-camera-permission/fr-FR-chrome.png",
},
},
my: {
silentZone: "Zone silencieuse",

View File

@ -57,6 +57,13 @@ const menu: NonNullable<Translation["menu"]> = {
language: {
title: "Langage",
},
privacySettings: {
title: "Paramètres du mode absent",
explanation:
"Quand l'onglet WorkAdventure n'est pas visible, vous passez en \"mode absent\". Lorsque ce mode est actif, vous pouvez décider de garder vos webcam et/ou micro désactivés tant que vous ne revenez pas sur l'onglet",
cameraToggle: "Camera",
microphoneToggle: "Microphone",
},
save: {
warning: "(La sauvegarde de ces paramètres redémarre le jeu)",
button: "Sauvegarder",

View File

@ -21,7 +21,6 @@ import type { Popup } from "./Api/iframe/Ui/Popup";
import type { Sound } from "./Api/iframe/Sound/Sound";
import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution";
import camera from "./Api/iframe/camera";
import {} from "./window";
const globalState = createState("global");
@ -183,6 +182,13 @@ const wa = {
export type WorkAdventureApi = typeof wa;
declare global {
interface Window {
WA: WorkAdventureApi;
}
let WA: WorkAdventureApi;
}
window.WA = wa;
window.addEventListener(

10
front/src/window.d.ts vendored
View File

@ -1,10 +0,0 @@
import { WorkAdventureApi } from "./iframe_api";
import { WorkAdventureDesktopApi } from "@wa-preload-app";
declare global {
interface Window {
WA: WorkAdventureApi;
WAD: WorkAdventureDesktopApi;
}
let WA: WorkAdventureApi;
}

View File

@ -12,6 +12,9 @@ export default defineConfig({
clientPort: 80,
},
},
build: {
sourcemap: true,
},
plugins: [
svelte({
preprocess: sveltePreprocess(),

View File

@ -7,27 +7,6 @@
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":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
dependencies:
"@babel/highlight" "^7.12.13"
"@babel/helper-validator-identifier@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
"@babel/highlight@^7.12.13":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf"
integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==
dependencies:
"@babel/helper-validator-identifier" "^7.14.0"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/runtime@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
@ -297,11 +276,6 @@
resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b"
integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prettier@^1.19.0":
version "1.19.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
@ -480,6 +454,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@ -494,6 +473,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
ansi-styles@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
anymatch@~3.1.1, anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@ -618,7 +602,7 @@ cancelable-promise@^4.2.1:
resolved "https://registry.yarnpkg.com/cancelable-promise/-/cancelable-promise-4.2.1.tgz#b02f79c5dde2704acfff1bc1ac2b4090f55541fe"
integrity sha512-PJZ/000ocWhPZQBAuNewAOMA2WEkJ8RhXI6AxeGLiGdW8EYDmumzo9wKyNgjDgxc1q/HbXuTdlcI+wXrOe/jMw==
chalk@^2.0.0, chalk@^2.4.1:
chalk@^2.4.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -627,7 +611,7 @@ chalk@^2.0.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
chalk@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
@ -685,6 +669,14 @@ cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cli-truncate@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389"
integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==
dependencies:
slice-ansi "^5.0.0"
string-width "^5.0.0"
clone@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
@ -714,15 +706,15 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
colorette@^2.0.16:
version "2.0.16"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==
commander@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
component-bind@1.0.0:
version "1.0.0"
@ -749,17 +741,6 @@ connect-history-api-fallback@^1.6.0:
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
cosmiconfig@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.2.1"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.10.0"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@ -818,11 +799,6 @@ debug@~3.1.0:
dependencies:
ms "2.0.0"
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-copy-ts@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/deep-copy-ts/-/deep-copy-ts-0.5.0.tgz#b9493d8e2bae85ef7d659c16eb707c13efb84499"
@ -876,6 +852,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
easystarjs@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/easystarjs/-/easystarjs-0.4.4.tgz#8cec6d20d0d8660715da0301d1da440370a8f40a"
@ -888,6 +869,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emoji-regex@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
engine.io-client@~3.5.0:
version "3.5.2"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.2.tgz#0ef473621294004e9ceebe73cef0af9e36f2f5fa"
@ -916,7 +902,7 @@ engine.io-parser@~2.2.0:
blob "0.0.5"
has-binary2 "~1.0.2"
enquirer@^2.3.5, enquirer@^2.3.6:
enquirer@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
@ -1234,10 +1220,10 @@ eventemitter3@^4.0.7:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
execa@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376"
integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
dependencies:
cross-spawn "^7.0.3"
get-stream "^6.0.0"
@ -1391,11 +1377,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
@ -1653,6 +1634,11 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-fullwidth-code-point@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
@ -1682,11 +1668,6 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
is-regex@^1.0.4, is-regex@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
@ -1695,11 +1676,6 @@ is-regex@^1.0.4, is-regex@^1.1.2:
call-bind "^1.0.2"
has-symbols "^1.0.2"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
is-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
@ -1717,11 +1693,6 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.2"
is-unicode-supported@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
isarray@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
@ -1745,11 +1716,6 @@ jasmine@^3.5.0:
glob "^7.1.6"
jasmine-core "~3.7.0"
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@ -1762,11 +1728,6 @@ json-parse-better-errors@^1.0.1:
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -1813,47 +1774,47 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
lilconfig@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
linked-list-typescript@^1.0.11:
version "1.0.15"
resolved "https://registry.yarnpkg.com/linked-list-typescript/-/linked-list-typescript-1.0.15.tgz#faeed93cf9203f102e2158c29edcddda320abe82"
integrity sha512-RIyUu9lnJIyIaMe63O7/aFv/T2v3KsMFuXMBbUQCHX+cgtGro86ETDj5ed0a8gQL2+DFjzYYsgVG4I36/cUwgw==
lint-staged@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.0.0.tgz#24d0a95aa316ba28e257f5c4613369a75a10c712"
integrity sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==
lint-staged@^12.3.7:
version "12.3.7"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.7.tgz#ad0e2014302f704f9cf2c0ebdb97ac63d0f17be0"
integrity sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==
dependencies:
chalk "^4.1.1"
cli-truncate "^2.1.0"
commander "^7.2.0"
cosmiconfig "^7.0.0"
debug "^4.3.1"
dedent "^0.7.0"
enquirer "^2.3.6"
execa "^5.0.0"
listr2 "^3.8.2"
log-symbols "^4.1.0"
cli-truncate "^3.1.0"
colorette "^2.0.16"
commander "^8.3.0"
debug "^4.3.3"
execa "^5.1.1"
lilconfig "2.0.4"
listr2 "^4.0.1"
micromatch "^4.0.4"
normalize-path "^3.0.0"
please-upgrade-node "^3.2.0"
string-argv "0.3.1"
stringify-object "^3.3.0"
object-inspect "^1.12.0"
pidtree "^0.5.0"
string-argv "^0.3.1"
supports-color "^9.2.1"
yaml "^1.10.2"
listr2@^3.8.2:
version "3.10.0"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.10.0.tgz#58105a53ed7fa1430d1b738c6055ef7bb006160f"
integrity sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw==
listr2@^4.0.1:
version "4.0.5"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5"
integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==
dependencies:
cli-truncate "^2.1.0"
colorette "^1.2.2"
colorette "^2.0.16"
log-update "^4.0.0"
p-map "^4.0.0"
rxjs "^6.6.7"
rfdc "^1.3.0"
rxjs "^7.5.5"
through "^2.3.8"
wrap-ansi "^7.0.0"
@ -1882,14 +1843,6 @@ lodash@^4.17.15:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log-symbols@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
dependencies:
chalk "^4.1.0"
is-unicode-supported "^0.1.0"
log-update@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
@ -2053,6 +2006,11 @@ object-hash@^1.3.1:
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
object-inspect@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
object-inspect@^1.9.0:
version "1.10.3"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
@ -2139,16 +2097,6 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
dependencies:
"@babel/code-frame" "^7.0.0"
error-ex "^1.3.1"
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parseqs@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
@ -2241,18 +2189,16 @@ pidtree@^0.3.0:
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
pidtree@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.5.0.tgz#ad5fbc1de78b8a5f99d6fbdd4f6e4eee21d1aca1"
integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
dependencies:
semver-compare "^1.0.0"
postcss@^8.4.5:
version "8.4.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
@ -2462,6 +2408,11 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rfdc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
rimraf@^2.5.2:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@ -2495,13 +2446,20 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rxjs@^6.6.3, rxjs@^6.6.7:
rxjs@^6.6.3:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
dependencies:
tslib "^1.9.0"
rxjs@^7.5.5:
version "7.5.5"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f"
integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==
dependencies:
tslib "^2.1.0"
sade@^1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691"
@ -2533,11 +2491,6 @@ sass@^1.49.7:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
"semver@2 || 3 || 4 || 5", semver@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@ -2620,6 +2573,14 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slice-ansi@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==
dependencies:
ansi-styles "^6.0.0"
is-fullwidth-code-point "^4.0.0"
socket.io-client@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35"
@ -2711,7 +2672,7 @@ standardized-audio-context@^25.2.4:
automation-events "^4.0.1"
tslib "^2.2.0"
string-argv@0.3.1:
string-argv@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
@ -2725,6 +2686,15 @@ string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string-width@^5.0.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
dependencies:
eastasianwidth "^0.2.0"
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
string.prototype.padend@^3.0.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz#6858ca4f35c5268ebd5e8615e1327d55f59ee311"
@ -2757,15 +2727,6 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
stringify-object@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
dependencies:
get-own-enumerable-property-symbols "^3.0.0"
is-obj "^1.0.1"
is-regexp "^1.0.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@ -2780,6 +2741,13 @@ strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
dependencies:
ansi-regex "^6.0.1"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@ -2816,6 +2784,11 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
supports-color@^9.2.1:
version "9.2.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891"
integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@ -2900,6 +2873,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ts-deferred@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/ts-deferred/-/ts-deferred-1.0.4.tgz#58145ebaeef5b8f2a290b8cec3d060839f9489c7"
integrity sha1-WBReuu71uPKikLjOw9Bgg5+Uicc=
ts-node@^10.4.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
@ -2967,6 +2945,11 @@ tslib@^2.0.0, tslib@^2.2.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@ -3178,7 +3161,7 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.0:
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==

BIN
maps/assets/audio/campfire.ogg Executable file

Binary file not shown.

View File

@ -0,0 +1,241 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 444, 444, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":6,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 444, 444, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":40,
"name":"no-audio",
"opacity":1,
"properties":[
{
"name":"startLayer",
"type":"bool",
"value":true
}],
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201],
"height":10,
"id":4,
"name":"floor",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449, 449],
"height":10,
"id":39,
"name":"audio",
"opacity":1,
"properties":[
{
"name":"audioLoop",
"type":"bool",
"value":true
},
{
"name":"playAudio",
"type":"string",
"value":"..\/..\/assets\/audio\/campfire.ogg"
}],
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"floorLayer",
"objects":[
{
"height":227.294588060257,
"id":9,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":13,
"text":"\"Allow audio\" button test case:\n\n1. Reload the page (to trigger the audio without a user gesture)\n2. You should see the \"Allow audio\" button\n3. Click on that button to allow the audio to be played\n\n(in some situations a user gesture is mandatory to play audio: like on mobile or on desktop without a user gesture in the same domain during the same session)\n\n(use #no-audio start layer to spawn outside the audio zone)",
"wrap":true
},
"type":"",
"visible":true,
"width":317.982661490298,
"x":1.53139349868751,
"y":29.8384428152397
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":41,
"nextobjectid":10,
"orientation":"orthogonal",
"properties":[
{
"name":"mapCopyright",
"type":"string",
"value":"Credits: Valdo Romao https:\/\/www.linkedin.com\/in\/valdo-romao\/ \nLicense: CC-BY-SA 3.0 (http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/)"
},
{
"name":"mapDescription",
"type":"string",
"value":"A perfect virtual office to get started with WorkAdventure!"
},
{
"name":"mapImage",
"type":"string",
"value":"map.png"
},
{
"name":"mapLink",
"type":"string",
"value":"https:\/\/thecodingmachine.github.io\/workadventure-map-starter-kit\/map.json"
},
{
"name":"mapName",
"type":"string",
"value":"Allow audio test"
}],
"renderorder":"right-down",
"tiledversion":"1.8.2",
"tileheight":32,
"tilesets":[
{
"columns":10,
"firstgid":1,
"image":"..\/..\/assets\/tileset5_export.png",
"imageheight":320,
"imagewidth":320,
"margin":0,
"name":"tileset5_export",
"properties":[
{
"name":"tilesetCopyright",
"type":"string",
"value":"\u00a9 2021 WorkAdventure <https:\/\/workadventu.re\/> \nLicence: WORKADVENTURE SPECIFIC RESOURCES LICENSE (see LICENSE.assets file)"
}],
"spacing":0,
"tilecount":100,
"tileheight":32,
"tilewidth":32
},
{
"columns":10,
"firstgid":101,
"image":"..\/..\/assets\/tileset6_export.png",
"imageheight":320,
"imagewidth":320,
"margin":0,
"name":"tileset6_export",
"properties":[
{
"name":"tilesetCopyright",
"type":"string",
"value":"\u00a9 2021 WorkAdventure <https:\/\/workadventu.re\/> \nLicence: WORKADVENTURE SPECIFIC RESOURCES LICENSE (see LICENSE.assets file)"
}],
"spacing":0,
"tilecount":100,
"tileheight":32,
"tilewidth":32
},
{
"columns":11,
"firstgid":201,
"image":"..\/..\/assets\/tileset1.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1",
"properties":[
{
"name":"tilesetCopyright",
"type":"string",
"value":"\u00a9 2021 WorkAdventure <https:\/\/workadventu.re\/> \nLicence: WORKADVENTURE SPECIFIC RESOURCES LICENSE (see LICENSE.assets file)"
}],
"spacing":0,
"tilecount":121,
"tileheight":32,
"tilewidth":32
},
{
"columns":11,
"firstgid":322,
"image":"..\/..\/assets\/tileset1-repositioning.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1-repositioning",
"properties":[
{
"name":"tilesetCopyright",
"type":"string",
"value":"\u00a9 2021 WorkAdventure <https:\/\/workadventu.re\/> \nLicence: WORKADVENTURE SPECIFIC RESOURCES LICENSE (see LICENSE.assets file)"
}],
"spacing":0,
"tilecount":121,
"tileheight":32,
"tilewidth":32
},
{
"columns":6,
"firstgid":443,
"image":"..\/..\/assets\/Special_Zones.png",
"imageheight":64,
"imagewidth":192,
"margin":0,
"name":"Special_Zones",
"properties":[
{
"name":"tilesetCopyright",
"type":"string",
"value":"\u00a9 2021 WorkAdventure <https:\/\/workadventu.re\/> \nLicence: WORKADVENTURE SPECIFIC RESOURCES LICENSE (see LICENSE.assets file)"
}],
"spacing":0,
"tilecount":12,
"tileheight":32,
"tiles":[
{
"id":0,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":"1.8",
"width":10
}

View File

@ -0,0 +1,97 @@
{ "compressionlevel":-1,
"height":20,
"infinite":false,
"layers":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":20,
"id":42,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":20,
"x":0,
"y":0
},
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":20,
"id":39,
"name":"floor",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":20,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":43,
"name":"Test",
"objects":[
{
"height":225.333333333333,
"id":13,
"name":"",
"rotation":0,
"text":
{
"text":"Test: \n- Open two windows (you can use Private Mode) so that you control two Wokas.\n\n- On woka A window: go to Menu > Settings and set your away mode options\n- On woka A window: open a new tab in the browser, so that your WA tab is not visible\n\n- On woka B window: move to woka A to check that the options were applied",
"wrap":true
},
"type":"",
"visible":true,
"width":434.773333333333,
"x":97.9466666666667,
"y":33.8366666666667
},
{
"height":155,
"id":16,
"name":"",
"rotation":0,
"text":
{
"color":"#00007f",
"text":"Reminder: \nThere are 4 cases to test for your away mode (WA tab hidden) settings. \nCamera and microphone stay enabled\nOnly camera stays enabled\nOnly microphone stays enabled\nBoth are disabled",
"wrap":true
},
"type":"",
"visible":true,
"width":407.4375,
"x":96.9479166666667,
"y":322.5
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":44,
"nextobjectid":17,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.7.2",
"tileheight":32,
"tilesets":[
{
"columns":11,
"firstgid":1,
"image":"..\/tileset1.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1",
"spacing":0,
"tilecount":121,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":"1.6",
"width":20
}

View File

@ -128,6 +128,14 @@
<a href="#" class="testLink" data-testmap="audio.json" target="_blank">Testing audio layer</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-allow-audio"> Success <input type="radio" name="test-allow-audio"> Failure <input type="radio" name="test-allow-audio" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="AllowAudio/map.json" target="_blank">Test audio on start zone and mobile</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-jitsi-silent"> Success <input type="radio" name="test-jitsi-silent"> Failure <input type="radio" name="test-jitsi-silent" checked> Pending
@ -455,6 +463,14 @@
<a href="#" class="testLink" data-testmap="mousewheel.json" target="_blank">Testing zoom via mouse wheel</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-mouse-wheel"> Success <input type="radio" name="test-mouse-wheel"> Failure <input type="radio" name="test-mouse-wheel" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="AwayModeSettings/away_mode_settings.json" target="_blank">Away mode settings</a>
</td>
</tr>
</table>
</div>
<script>

View File

@ -22,6 +22,10 @@ export const isMapDetailsData = new tg.IsInterface()
expireOn: tg.isString,
// Whether the "report" feature is enabled or not on this room
canReport: tg.isBoolean,
// The URL of the logo image on the loading screen
loadingLogo: tg.isNullable(tg.isString),
// The URL of the logo image on "LoginScene"
loginSceneLogo: tg.isNullable(tg.isString),
})
.get();

View File

@ -21,7 +21,8 @@
"generic-type-guard": "^3.5.0",
"google-protobuf": "^3.13.0",
"grpc": "^1.24.4",
"ts-proto": "^1.96.0"
"ts-proto": "^1.96.0",
"zod": "^3.12.0"
},
"devDependencies": {
"@types/google-protobuf": "^3.7.4",

View File

@ -99,6 +99,10 @@ message FollowAbortMessage {
int32 follower = 2;
}
message LockGroupPromptMessage {
bool lock = 1;
}
message ClientToServerMessage {
oneof message {
UserMovesMessage userMovesMessage = 2;
@ -117,6 +121,7 @@ message ClientToServerMessage {
FollowRequestMessage followRequestMessage = 15;
FollowConfirmationMessage followConfirmationMessage = 16;
FollowAbortMessage followAbortMessage = 17;
LockGroupPromptMessage lockGroupPromptMessage = 18;
}
}
@ -185,6 +190,7 @@ message GroupUpdateMessage {
int32 groupId = 1;
PointMessage position = 2;
int32 groupSize = 3;
bool locked = 4;
}
message GroupDeleteMessage {
@ -216,6 +222,11 @@ message ItemStateMessage {
string stateJson = 2;
}
message GroupUsersUpdateMessage {
int32 groupId = 1;
repeated int32 userIds = 2;
}
message RoomJoinedMessage {
//repeated UserJoinedMessage user = 1;
//repeated GroupUpdateMessage group = 2;
@ -316,6 +327,7 @@ message ServerToClientMessage {
FollowConfirmationMessage followConfirmationMessage = 22;
FollowAbortMessage followAbortMessage = 23;
InvalidTextureMessage invalidTextureMessage = 24;
GroupUsersUpdateMessage groupUsersUpdateMessage = 25;
}
}
@ -356,8 +368,9 @@ message UserLeftZoneMessage {
message GroupUpdateZoneMessage {
int32 groupId = 1;
PointMessage position = 2;
int32 groupSize = 3;
int32 groupSize = 3;
Zone fromZone = 4;
bool locked = 5;
}
message GroupLeftZoneMessage {
@ -403,6 +416,7 @@ message PusherToBackMessage {
FollowRequestMessage followRequestMessage = 16;
FollowConfirmationMessage followConfirmationMessage = 17;
FollowAbortMessage followAbortMessage = 18;
LockGroupPromptMessage lockGroupPromptMessage = 19;
}
}

View File

@ -4645,3 +4645,8 @@ year@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0"
integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A=
zod@^3.12.0:
version "3.14.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2"
integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw==

View File

@ -21,6 +21,7 @@ import {
FollowConfirmationMessage,
FollowAbortMessage,
VariableMessage,
LockGroupPromptMessage,
} from "../Messages/generated/messages_pb";
import { UserMovesMessage } from "../Messages/generated/messages_pb";
import { parse } from "query-string";
@ -37,7 +38,7 @@ import { InvalidTokenError } from "../Controller/InvalidTokenError";
import HyperExpress from "hyper-express";
import { localWokaService } from "../Services/LocalWokaService";
import { WebSocket } from "uWebSockets.js";
import { WokaDetail } from "../Enum/PlayerTextures";
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
/**
* The object passed between the "open" and the "upgrade" methods when opening a websocket
@ -561,6 +562,11 @@ export class IoSocketController {
);
} else if (message.hasFollowabortmessage()) {
socketManager.handleFollowAbort(client, message.getFollowabortmessage() as FollowAbortMessage);
} else if (message.hasLockgrouppromptmessage()) {
socketManager.handleLockGroup(
client,
message.getLockgrouppromptmessage() as LockGroupPromptMessage
);
}
/* Ok is false if backpressure was built up, wait for drain */

View File

@ -88,6 +88,14 @@ export class MapController extends BaseHttpController {
* type: boolean|undefined
* description: Whether the "report" feature is enabled or not on this room
* example: true
* loadingLogo:
* type: string
* description: The URL of the image to be used on the loading page
* example: https://example.com/logo.png
* loginSceneLogo:
* type: string
* description: The URL of the image to be used on the LoginScene
* example: https://example.com/logo_login.png
*
*/
this.app.get("/map", (req, res) => {

View File

@ -92,8 +92,10 @@ export class PositionDispatcher {
public removeViewport(socket: ExSocketInterface): void {
// Also, let's stop listening on viewports
for (const zone of socket.listenedZones) {
this.stopListening(zone, socket);
if (socket.listenedZones) {
for (const zone of socket.listenedZones) {
this.stopListening(zone, socket);
}
}
}

View File

@ -11,7 +11,7 @@ import {
import { ClientDuplexStream } from "grpc";
import { Zone } from "_Model/Zone";
import { compressors } from "hyper-express";
import { WokaDetail } from "_Enum/PlayerTextures";
import { WokaDetail } from "../../Messages/JsonMessages/PlayerTextures";
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;

View File

@ -9,7 +9,7 @@ import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface";
import Direction = PositionMessage.Direction;
import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage";
import { PositionInterface } from "_Model/PositionInterface";
import { WokaDetail } from "_Enum/PlayerTextures";
import { WokaDetail } from "../../Messages/JsonMessages/PlayerTextures";
export class ProtobufUtils {
public static toPositionMessage(point: PointInterface): PositionMessage {

View File

@ -123,19 +123,25 @@ export class UserDescriptor {
}
export class GroupDescriptor {
private constructor(public readonly groupId: number, private groupSize: number, private position: PointMessage) {}
private constructor(
public readonly groupId: number,
private groupSize: number,
private position: PointMessage,
private locked: boolean
) {}
public static createFromGroupUpdateZoneMessage(message: GroupUpdateZoneMessage): GroupDescriptor {
const position = message.getPosition();
if (position === undefined) {
throw new Error("Missing position");
}
return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position);
return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position, message.getLocked());
}
public update(groupDescriptor: GroupDescriptor) {
this.groupSize = groupDescriptor.groupSize;
this.position = groupDescriptor.position;
this.locked = groupDescriptor.locked;
}
public toGroupUpdateMessage(): GroupUpdateMessage {
@ -146,7 +152,7 @@ export class GroupDescriptor {
groupUpdateMessage.setGroupid(this.groupId);
groupUpdateMessage.setGroupsize(this.groupSize);
groupUpdateMessage.setPosition(this.position);
groupUpdateMessage.setLocked(this.locked);
return groupUpdateMessage;
}
}
@ -206,9 +212,7 @@ export class Zone {
this.notifyGroupMove(groupDescriptor);
} else {
this.groups.set(groupId, groupDescriptor);
const fromZone = groupUpdateZoneMessage.getFromzone();
this.notifyGroupEnter(groupDescriptor, fromZone?.toObject());
}
} else if (message.hasUserleftzonemessage()) {

Some files were not shown because too many files have changed in this diff Show More