merged develop
4
.github/workflows/continuous_integration.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
- name: "Build proto messages"
|
- name: "Build proto messages"
|
||||||
run: yarn run proto && yarn run copy-to-front
|
run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front
|
||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
- name: "Create index.html"
|
- name: "Create index.html"
|
||||||
@ -97,7 +97,7 @@ jobs:
|
|||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
- name: "Build proto messages"
|
- name: "Build proto messages"
|
||||||
run: yarn run proto && yarn run copy-to-pusher
|
run: yarn run proto && yarn run copy-to-pusher && yarn run json-copy-to-pusher
|
||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
|
59
.github/workflows/end_to_end_tests.yml
vendored
@ -11,10 +11,43 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
start-runner:
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
||||||
|
name: Start self-hosted EC2 runner
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
label: ${{ steps.start-ec2-runner.outputs.label }}
|
||||||
|
ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
- name: Start EC2 runner
|
||||||
|
id: start-ec2-runner
|
||||||
|
uses: machulav/ec2-github-runner@v2
|
||||||
|
with:
|
||||||
|
mode: start
|
||||||
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||||
|
ec2-image-id: ami-094dbcc53250a2480
|
||||||
|
ec2-instance-type: t3.xlarge
|
||||||
|
subnet-id: subnet-0ac40025f559df1bc
|
||||||
|
security-group-id: sg-0e36e96e3b8ed2d64
|
||||||
|
#iam-role-name: my-role-name # optional, requires additional permissions
|
||||||
|
#aws-resource-tags: > # optional, requires additional permissions
|
||||||
|
# [
|
||||||
|
# {"Key": "Name", "Value": "ec2-github-runner"},
|
||||||
|
# {"Key": "GitHubRepository", "Value": "${{ github.repository }}"}
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
end-to-end-tests:
|
end-to-end-tests:
|
||||||
name: "End-to-end testcafe tests"
|
name: "End-to-end testcafe tests"
|
||||||
|
|
||||||
runs-on: "ubuntu-latest"
|
needs: start-runner # required to start the main job when the runner is ready
|
||||||
|
runs-on: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
@ -67,3 +100,27 @@ jobs:
|
|||||||
- name: Display logs
|
- name: Display logs
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
run: docker-compose logs
|
run: docker-compose logs
|
||||||
|
|
||||||
|
stop-runner:
|
||||||
|
name: Stop self-hosted EC2 runner
|
||||||
|
needs:
|
||||||
|
- start-runner # required to get output from the start-runner job
|
||||||
|
- end-to-end-tests # required to wait when the main job is done
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
- name: Stop EC2 runner
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
||||||
|
uses: machulav/ec2-github-runner@v2
|
||||||
|
with:
|
||||||
|
mode: stop
|
||||||
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||||
|
label: ${{ needs.start-runner.outputs.label }}
|
||||||
|
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
|
||||||
|
2
.github/workflows/push-to-npm.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
- name: "Build proto messages"
|
- name: "Build proto messages"
|
||||||
run: yarn run proto && yarn run copy-to-front
|
run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front
|
||||||
working-directory: "messages"
|
working-directory: "messages"
|
||||||
|
|
||||||
- name: "Create index.html"
|
- name: "Create index.html"
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd messages || exit
|
||||||
|
yarn run precommit
|
||||||
|
)
|
||||||
(
|
(
|
||||||
cd front || exit
|
cd front || exit
|
||||||
yarn run precommit
|
yarn run precommit
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "error"
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"no-throw-literal": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# protobuf build
|
# protobuf build
|
||||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY messages .
|
COPY messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
# typescript build
|
# typescript build
|
||||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY back/yarn.lock back/package.json ./
|
COPY back/yarn.lock back/package.json ./
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
@ -15,7 +15,7 @@ ENV NODE_ENV=production
|
|||||||
RUN yarn run tsc
|
RUN yarn run tsc
|
||||||
|
|
||||||
# final production image
|
# final production image
|
||||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY back/yarn.lock back/package.json ./
|
COPY back/yarn.lock back/package.json ./
|
||||||
COPY --from=builder2 /usr/src/dist /usr/src/dist
|
COPY --from=builder2 /usr/src/dist /usr/src/dist
|
||||||
|
@ -68,14 +68,14 @@
|
|||||||
"@types/mkdirp": "^1.0.1",
|
"@types/mkdirp": "^1.0.1",
|
||||||
"@types/redis": "^2.8.31",
|
"@types/redis": "^2.8.31",
|
||||||
"@types/uuidv4": "^5.0.0",
|
"@types/uuidv4": "^5.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^5.8.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^5.8.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.5.0",
|
||||||
"jasmine": "^3.5.0",
|
"jasmine": "^3.5.0",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"ts-node-dev": "^1.0.0-pre.44",
|
"ts-node-dev": "^1.1.8",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.ts": [
|
"*.ts": [
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
// lib/server.ts
|
// lib/server.ts
|
||||||
import App from "./src/App";
|
import App from "./src/App";
|
||||||
import grpc from "grpc";
|
import grpc from "grpc";
|
||||||
import {roomManager} from "./src/RoomManager";
|
import { roomManager } from "./src/RoomManager";
|
||||||
import {IRoomManagerServer, RoomManagerService} from "./src/Messages/generated/messages_grpc_pb";
|
import { IRoomManagerServer, RoomManagerService } from "./src/Messages/generated/messages_grpc_pb";
|
||||||
import {HTTP_PORT, GRPC_PORT} from "./src/Enum/EnvironmentVariable";
|
import { HTTP_PORT, GRPC_PORT } from "./src/Enum/EnvironmentVariable";
|
||||||
|
|
||||||
App.listen(HTTP_PORT, () => console.log(`WorkAdventure HTTP API starting on port %d!`, HTTP_PORT))
|
App.listen(HTTP_PORT, () => console.log(`WorkAdventure HTTP API starting on port %d!`, HTTP_PORT));
|
||||||
|
|
||||||
const server = new grpc.Server();
|
const server = new grpc.Server();
|
||||||
server.addService<IRoomManagerServer>(RoomManagerService, roomManager);
|
server.addService<IRoomManagerServer>(RoomManagerService, roomManager);
|
||||||
|
|
||||||
server.bind('0.0.0.0:'+GRPC_PORT, grpc.ServerCredentials.createInsecure());
|
server.bind(`0.0.0.0:${GRPC_PORT}`, grpc.ServerCredentials.createInsecure());
|
||||||
server.start();
|
server.start();
|
||||||
console.log('WorkAdventure HTTP/2 API starting on port %d!', GRPC_PORT);
|
console.log("WorkAdventure HTTP/2 API starting on port %d!", GRPC_PORT);
|
||||||
|
@ -36,9 +36,11 @@ export class DebugController {
|
|||||||
return "BatchedMessages";
|
return "BatchedMessages";
|
||||||
}
|
}
|
||||||
if (value instanceof Map) {
|
if (value instanceof Map) {
|
||||||
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
const obj: { [key: string | number]: unknown } = {};
|
||||||
for (const [mapKey, mapValue] of value.entries()) {
|
for (const [mapKey, mapValue] of value.entries()) {
|
||||||
obj[mapKey] = mapValue;
|
if (typeof mapKey === "number" || typeof mapKey === "string") {
|
||||||
|
obj[mapKey] = mapValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
} else if (value instanceof Set) {
|
} else if (value instanceof Set) {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { App } from "../Server/sifrr.server";
|
import { App } from "../Server/sifrr.server";
|
||||||
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||||
const register = require("prom-client").register;
|
import { register, collectDefaultMetrics } from "prom-client";
|
||||||
const collectDefaultMetrics = require("prom-client").collectDefaultMetrics;
|
|
||||||
|
|
||||||
export class PrometheusController {
|
export class PrometheusController {
|
||||||
constructor(private App: App) {
|
constructor(private App: App) {
|
||||||
collectDefaultMetrics({
|
collectDefaultMetrics({
|
||||||
timeout: 10000,
|
|
||||||
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,7 +2,13 @@ import { PointInterface } from "./Websocket/PointInterface";
|
|||||||
import { Group } from "./Group";
|
import { Group } from "./Group";
|
||||||
import { User, UserSocket } from "./User";
|
import { User, UserSocket } from "./User";
|
||||||
import { PositionInterface } from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone";
|
import {
|
||||||
|
EmoteCallback,
|
||||||
|
EntersCallback,
|
||||||
|
LeavesCallback,
|
||||||
|
MovesCallback,
|
||||||
|
PlayerDetailsUpdatedCallback,
|
||||||
|
} from "_Model/Zone";
|
||||||
import { PositionNotifier } from "./PositionNotifier";
|
import { PositionNotifier } from "./PositionNotifier";
|
||||||
import { Movable } from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {
|
import {
|
||||||
@ -11,9 +17,11 @@ import {
|
|||||||
EmoteEventMessage,
|
EmoteEventMessage,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
SubToPusherRoomMessage,
|
SubToPusherRoomMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
VariableWithTagMessage,
|
VariableWithTagMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { RoomSocket, ZoneSocket } from "src/RoomManager";
|
import { RoomSocket, ZoneSocket } from "src/RoomManager";
|
||||||
@ -27,6 +35,7 @@ import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
|||||||
import { LocalUrlError } from "../Services/LocalUrlError";
|
import { LocalUrlError } from "../Services/LocalUrlError";
|
||||||
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
|
||||||
import { VariableError } from "../Services/VariableError";
|
import { VariableError } from "../Services/VariableError";
|
||||||
|
import { isRoomRedirect } from "../Services/AdminApi/RoomRedirect";
|
||||||
|
|
||||||
export type ConnectCallback = (user: User, group: Group) => void;
|
export type ConnectCallback = (user: User, group: Group) => void;
|
||||||
export type DisconnectCallback = (user: User, group: Group) => void;
|
export type DisconnectCallback = (user: User, group: Group) => void;
|
||||||
@ -56,10 +65,19 @@ export class GameRoom {
|
|||||||
onEnters: EntersCallback,
|
onEnters: EntersCallback,
|
||||||
onMoves: MovesCallback,
|
onMoves: MovesCallback,
|
||||||
onLeaves: LeavesCallback,
|
onLeaves: LeavesCallback,
|
||||||
onEmote: EmoteCallback
|
onEmote: EmoteCallback,
|
||||||
|
onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
|
||||||
) {
|
) {
|
||||||
// A zone is 10 sprites wide.
|
// A zone is 10 sprites wide.
|
||||||
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves, onEmote);
|
this.positionNotifier = new PositionNotifier(
|
||||||
|
320,
|
||||||
|
320,
|
||||||
|
onEnters,
|
||||||
|
onMoves,
|
||||||
|
onLeaves,
|
||||||
|
onEmote,
|
||||||
|
onPlayerDetailsUpdated
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async create(
|
public static async create(
|
||||||
@ -71,7 +89,8 @@ export class GameRoom {
|
|||||||
onEnters: EntersCallback,
|
onEnters: EntersCallback,
|
||||||
onMoves: MovesCallback,
|
onMoves: MovesCallback,
|
||||||
onLeaves: LeavesCallback,
|
onLeaves: LeavesCallback,
|
||||||
onEmote: EmoteCallback
|
onEmote: EmoteCallback,
|
||||||
|
onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
|
||||||
): Promise<GameRoom> {
|
): Promise<GameRoom> {
|
||||||
const mapDetails = await GameRoom.getMapDetails(roomUrl);
|
const mapDetails = await GameRoom.getMapDetails(roomUrl);
|
||||||
|
|
||||||
@ -85,16 +104,13 @@ export class GameRoom {
|
|||||||
onEnters,
|
onEnters,
|
||||||
onMoves,
|
onMoves,
|
||||||
onLeaves,
|
onLeaves,
|
||||||
onEmote
|
onEmote,
|
||||||
|
onPlayerDetailsUpdated
|
||||||
);
|
);
|
||||||
|
|
||||||
return gameRoom;
|
return gameRoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGroups(): Group[] {
|
|
||||||
return Array.from(this.groups.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
public getUsers(): Map<number, User> {
|
public getUsers(): Map<number, User> {
|
||||||
return this.users;
|
return this.users;
|
||||||
}
|
}
|
||||||
@ -157,6 +173,14 @@ export class GameRoom {
|
|||||||
if (userObj !== undefined && typeof userObj.group !== "undefined") {
|
if (userObj !== undefined && typeof userObj.group !== "undefined") {
|
||||||
this.leaveGroup(userObj);
|
this.leaveGroup(userObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.hasFollowers()) {
|
||||||
|
user.stopLeading();
|
||||||
|
}
|
||||||
|
if (user.following) {
|
||||||
|
user.following.delFollower(user);
|
||||||
|
}
|
||||||
|
|
||||||
this.users.delete(user.id);
|
this.users.delete(user.id);
|
||||||
this.usersByUuid.delete(user.uuid);
|
this.usersByUuid.delete(user.uuid);
|
||||||
|
|
||||||
@ -180,6 +204,14 @@ export class GameRoom {
|
|||||||
this.updateUserGroup(user);
|
this.updateUserGroup(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePlayerDetails(user: User, playerDetailsMessage: SetPlayerDetailsMessage) {
|
||||||
|
if (playerDetailsMessage.getRemoveoutlinecolor()) {
|
||||||
|
user.outlineColor = undefined;
|
||||||
|
} else {
|
||||||
|
user.outlineColor = playerDetailsMessage.getOutlinecolor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateUserGroup(user: User): void {
|
private updateUserGroup(user: User): void {
|
||||||
user.group?.updatePosition();
|
user.group?.updatePosition();
|
||||||
user.group?.searchForNearbyUsers();
|
user.group?.searchForNearbyUsers();
|
||||||
@ -187,8 +219,8 @@ export class GameRoom {
|
|||||||
if (user.silent) {
|
if (user.silent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const group = user.group;
|
||||||
if (user.group === undefined) {
|
if (group === undefined) {
|
||||||
// If the user is not part of a group:
|
// If the user is not part of a group:
|
||||||
// should he join a group?
|
// should he join a group?
|
||||||
|
|
||||||
@ -219,13 +251,40 @@ export class GameRoom {
|
|||||||
} else {
|
} else {
|
||||||
// If the user is part of a group:
|
// If the user is part of a group:
|
||||||
// should he leave the group?
|
// should he leave the group?
|
||||||
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition());
|
let noOneOutOfBounds = true;
|
||||||
if (distance > this.groupRadius) {
|
group.getUsers().forEach((foreignUser: User) => {
|
||||||
this.leaveGroup(user);
|
if (foreignUser.group === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const usrPos = foreignUser.getPosition();
|
||||||
|
const grpPos = foreignUser.group.getPosition();
|
||||||
|
const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos);
|
||||||
|
|
||||||
|
if (distance > this.groupRadius) {
|
||||||
|
if (foreignUser.hasFollowers() || foreignUser.following) {
|
||||||
|
// If one user is out of the group bounds BUT following, the group still exists... but should be hidden.
|
||||||
|
// We put it in 'outOfBounds' mode
|
||||||
|
group.setOutOfBounds(true);
|
||||||
|
noOneOutOfBounds = false;
|
||||||
|
} else {
|
||||||
|
this.leaveGroup(foreignUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (noOneOutOfBounds && !user.group?.isEmpty()) {
|
||||||
|
group.setOutOfBounds(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void {
|
||||||
|
user.group?.getUsers().forEach((currentUser: User) => {
|
||||||
|
if (currentUser.id !== user.id) {
|
||||||
|
currentUser.socket.write(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setSilent(user: User, silent: boolean) {
|
setSilent(user: User, silent: boolean) {
|
||||||
if (user.silent === silent) {
|
if (user.silent === silent) {
|
||||||
return;
|
return;
|
||||||
@ -253,12 +312,9 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
group.leave(user);
|
group.leave(user);
|
||||||
if (group.isEmpty()) {
|
if (group.isEmpty()) {
|
||||||
this.positionNotifier.leave(group);
|
|
||||||
group.destroy();
|
group.destroy();
|
||||||
if (!this.groups.has(group)) {
|
if (!this.groups.has(group)) {
|
||||||
throw new Error(
|
throw new Error(`Could not find group ${group.getId()} referenced by user ${user.id} in World.`);
|
||||||
"Could not find group " + group.getId() + " referenced by user " + user.id + " in World."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.groups.delete(group);
|
this.groups.delete(group);
|
||||||
//todo: is the group garbage collected?
|
//todo: is the group garbage collected?
|
||||||
@ -459,9 +515,9 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await adminApi.fetchMapDetails(roomUrl);
|
const result = await adminApi.fetchMapDetails(roomUrl);
|
||||||
if (!isMapDetailsData(result)) {
|
if (isRoomRedirect(result)) {
|
||||||
console.error("Unexpected room details received from server", result);
|
console.error("Unexpected room redirect received while querying map details", result);
|
||||||
throw new Error("Unexpected room details received from server");
|
throw new Error("Unexpected room redirect received while querying map details");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,10 @@ export class Group implements Movable {
|
|||||||
private wasDestroyed: boolean = false;
|
private wasDestroyed: boolean = false;
|
||||||
private roomId: string;
|
private roomId: string;
|
||||||
private currentZone: Zone | null = null;
|
private currentZone: Zone | null = null;
|
||||||
|
/**
|
||||||
|
* When outOfBounds = true, a user if out of the bounds of the group BUT still considered inside it (because we are in following mode)
|
||||||
|
*/
|
||||||
|
private outOfBounds = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
@ -78,6 +82,10 @@ export class Group implements Movable {
|
|||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
|
||||||
|
if (this.outOfBounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldX === undefined) {
|
if (oldX === undefined) {
|
||||||
this.currentZone = this.positionNotifier.enter(this);
|
this.currentZone = this.positionNotifier.enter(this);
|
||||||
} else {
|
} else {
|
||||||
@ -116,7 +124,7 @@ export class Group implements Movable {
|
|||||||
leave(user: User): void {
|
leave(user: User): void {
|
||||||
const success = this.users.delete(user);
|
const success = this.users.delete(user);
|
||||||
if (success === false) {
|
if (success === false) {
|
||||||
throw new Error("Could not find user " + user.id + " in the group " + this.id);
|
throw new Error(`Could not find user ${user.id} in the group ${this.id}`);
|
||||||
}
|
}
|
||||||
user.group = undefined;
|
user.group = undefined;
|
||||||
|
|
||||||
@ -133,6 +141,10 @@ export class Group implements Movable {
|
|||||||
* Usually used when there is only one user left.
|
* Usually used when there is only one user left.
|
||||||
*/
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
|
if (!this.outOfBounds) {
|
||||||
|
this.positionNotifier.leave(this);
|
||||||
|
}
|
||||||
|
|
||||||
for (const user of this.users) {
|
for (const user of this.users) {
|
||||||
this.leave(user);
|
this.leave(user);
|
||||||
}
|
}
|
||||||
@ -142,4 +154,26 @@ export class Group implements Movable {
|
|||||||
get getSize() {
|
get getSize() {
|
||||||
return this.users.size;
|
return this.users.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group can have at most one person leading the way in it.
|
||||||
|
*/
|
||||||
|
get leader(): User | undefined {
|
||||||
|
for (const user of this.users) {
|
||||||
|
if (user.hasFollowers()) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutOfBounds(outOfBounds: boolean): void {
|
||||||
|
if (this.outOfBounds === true && outOfBounds === false) {
|
||||||
|
this.positionNotifier.enter(this);
|
||||||
|
this.outOfBounds = false;
|
||||||
|
} else if (this.outOfBounds === false && outOfBounds === true) {
|
||||||
|
this.positionNotifier.leave(this);
|
||||||
|
this.outOfBounds = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,19 @@
|
|||||||
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
||||||
* number of players around the current player.
|
* number of players around the current player.
|
||||||
*/
|
*/
|
||||||
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } from "./Zone";
|
import {
|
||||||
|
EmoteCallback,
|
||||||
|
EntersCallback,
|
||||||
|
LeavesCallback,
|
||||||
|
MovesCallback,
|
||||||
|
PlayerDetailsUpdatedCallback,
|
||||||
|
Zone,
|
||||||
|
} from "./Zone";
|
||||||
import { Movable } from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import { PositionInterface } from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import { ZoneSocket } from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import { User } from "../Model/User";
|
import { User } from "../Model/User";
|
||||||
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage, SetPlayerDetailsMessage } from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
interface ZoneDescriptor {
|
interface ZoneDescriptor {
|
||||||
i: number;
|
i: number;
|
||||||
@ -42,7 +49,8 @@ export class PositionNotifier {
|
|||||||
private onUserEnters: EntersCallback,
|
private onUserEnters: EntersCallback,
|
||||||
private onUserMoves: MovesCallback,
|
private onUserMoves: MovesCallback,
|
||||||
private onUserLeaves: LeavesCallback,
|
private onUserLeaves: LeavesCallback,
|
||||||
private onEmote: EmoteCallback
|
private onEmote: EmoteCallback,
|
||||||
|
private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
||||||
@ -98,7 +106,15 @@ export class PositionNotifier {
|
|||||||
|
|
||||||
let zone = this.zones[j][i];
|
let zone = this.zones[j][i];
|
||||||
if (zone === undefined) {
|
if (zone === undefined) {
|
||||||
zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves, this.onEmote, i, j);
|
zone = new Zone(
|
||||||
|
this.onUserEnters,
|
||||||
|
this.onUserMoves,
|
||||||
|
this.onUserLeaves,
|
||||||
|
this.onEmote,
|
||||||
|
this.onPlayerDetailsUpdated,
|
||||||
|
i,
|
||||||
|
j
|
||||||
|
);
|
||||||
this.zones[j][i] = zone;
|
this.zones[j][i] = zone;
|
||||||
}
|
}
|
||||||
return zone;
|
return zone;
|
||||||
@ -132,4 +148,11 @@ export class PositionNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updatePlayerDetails(user: User, playerDetails: SetPlayerDetailsMessage) {
|
||||||
|
const position = user.getPosition();
|
||||||
|
const zoneDesc = this.getZoneDescriptorFromCoordinates(position.x, position.y);
|
||||||
|
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
||||||
|
zone.updatePlayerDetails(user, playerDetails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,11 @@ import { ServerDuplexStream } from "grpc";
|
|||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
CompanionMessage,
|
CompanionMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
SubMessage,
|
SubMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { CharacterLayer } from "_Model/Websocket/CharacterLayer";
|
import { CharacterLayer } from "_Model/Websocket/CharacterLayer";
|
||||||
@ -18,6 +21,8 @@ export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientM
|
|||||||
export class User implements Movable {
|
export class User implements Movable {
|
||||||
public listenedZones: Set<Zone>;
|
public listenedZones: Set<Zone>;
|
||||||
public group?: Group;
|
public group?: Group;
|
||||||
|
private _following: User | undefined;
|
||||||
|
private followedBy: Set<User> = new Set<User>();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public id: number,
|
public id: number,
|
||||||
@ -31,7 +36,8 @@ export class User implements Movable {
|
|||||||
public readonly visitCardUrl: string | null,
|
public readonly visitCardUrl: string | null,
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly characterLayers: CharacterLayer[],
|
public readonly characterLayers: CharacterLayer[],
|
||||||
public readonly companion?: CompanionMessage
|
public readonly companion?: CompanionMessage,
|
||||||
|
private _outlineColor?: number | undefined
|
||||||
) {
|
) {
|
||||||
this.listenedZones = new Set<Zone>();
|
this.listenedZones = new Set<Zone>();
|
||||||
|
|
||||||
@ -48,6 +54,45 @@ export class User implements Movable {
|
|||||||
this.positionNotifier.updatePosition(this, position, oldPosition);
|
this.positionNotifier.updatePosition(this, position, oldPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addFollower(follower: User): void {
|
||||||
|
this.followedBy.add(follower);
|
||||||
|
follower._following = this;
|
||||||
|
|
||||||
|
const message = new FollowConfirmationMessage();
|
||||||
|
message.setFollower(follower.id);
|
||||||
|
message.setLeader(this.id);
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setFollowconfirmationmessage(message);
|
||||||
|
this.socket.write(clientMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delFollower(follower: User): void {
|
||||||
|
this.followedBy.delete(follower);
|
||||||
|
follower._following = undefined;
|
||||||
|
|
||||||
|
const message = new FollowAbortMessage();
|
||||||
|
message.setFollower(follower.id);
|
||||||
|
message.setLeader(this.id);
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setFollowabortmessage(message);
|
||||||
|
this.socket.write(clientMessage);
|
||||||
|
follower.socket.write(clientMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasFollowers(): boolean {
|
||||||
|
return this.followedBy.size !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get following(): User | undefined {
|
||||||
|
return this._following;
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopLeading(): void {
|
||||||
|
for (const follower of this.followedBy) {
|
||||||
|
this.delFollower(follower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private batchedMessages: BatchMessage = new BatchMessage();
|
private batchedMessages: BatchMessage = new BatchMessage();
|
||||||
private batchTimeout: NodeJS.Timeout | null = null;
|
private batchTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
@ -69,4 +114,17 @@ export class User implements Movable {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public set outlineColor(value: number | undefined) {
|
||||||
|
this._outlineColor = value;
|
||||||
|
|
||||||
|
const playerDetails = new SetPlayerDetailsMessage();
|
||||||
|
if (value === undefined) {
|
||||||
|
playerDetails.setRemoveoutlinecolor(true);
|
||||||
|
} else {
|
||||||
|
playerDetails.setOutlinecolor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.positionNotifier.updatePlayerDetails(this, playerDetails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,20 @@ import { PositionInterface } from "_Model/PositionInterface";
|
|||||||
import { Movable } from "./Movable";
|
import { Movable } from "./Movable";
|
||||||
import { Group } from "./Group";
|
import { Group } from "./Group";
|
||||||
import { ZoneSocket } from "../RoomManager";
|
import { ZoneSocket } from "../RoomManager";
|
||||||
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
|
import {
|
||||||
|
EmoteEventMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
|
PlayerDetailsUpdatedMessage,
|
||||||
|
} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => void;
|
export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => void;
|
||||||
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
||||||
export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void;
|
export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void;
|
||||||
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
||||||
|
export type PlayerDetailsUpdatedCallback = (
|
||||||
|
playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage,
|
||||||
|
listener: ZoneSocket
|
||||||
|
) => void;
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
private things: Set<Movable> = new Set<Movable>();
|
private things: Set<Movable> = new Set<Movable>();
|
||||||
@ -19,6 +27,7 @@ export class Zone {
|
|||||||
private onMoves: MovesCallback,
|
private onMoves: MovesCallback,
|
||||||
private onLeaves: LeavesCallback,
|
private onLeaves: LeavesCallback,
|
||||||
private onEmote: EmoteCallback,
|
private onEmote: EmoteCallback,
|
||||||
|
private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback,
|
||||||
public readonly x: number,
|
public readonly x: number,
|
||||||
public readonly y: number
|
public readonly y: number
|
||||||
) {}
|
) {}
|
||||||
@ -30,21 +39,13 @@ export class Zone {
|
|||||||
const result = this.things.delete(thing);
|
const result = this.things.delete(thing);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
throw new Error("Could not find user in zone " + thing.id);
|
throw new Error(`Could not find user in zone ${thing.id}`);
|
||||||
}
|
}
|
||||||
if (thing instanceof Group) {
|
if (thing instanceof Group) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Could not find group " +
|
`Could not find group ${thing.getId()} in zone (${this.x},${this.y}). Position of group: (${
|
||||||
thing.getId() +
|
thing.getPosition().x
|
||||||
" in zone (" +
|
},${thing.getPosition().y})`
|
||||||
this.x +
|
|
||||||
"," +
|
|
||||||
this.y +
|
|
||||||
"). Position of group: (" +
|
|
||||||
thing.getPosition().x +
|
|
||||||
"," +
|
|
||||||
thing.getPosition().y +
|
|
||||||
")"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,4 +107,14 @@ export class Zone {
|
|||||||
this.onEmote(emoteEventMessage, listener);
|
this.onEmote(emoteEventMessage, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updatePlayerDetails(user: User, playerDetails: SetPlayerDetailsMessage) {
|
||||||
|
const playerDetailsUpdatedMessage = new PlayerDetailsUpdatedMessage();
|
||||||
|
playerDetailsUpdatedMessage.setUserid(user.id);
|
||||||
|
playerDetailsUpdatedMessage.setDetails(playerDetails);
|
||||||
|
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
this.onPlayerDetailsUpdated(playerDetailsUpdatedMessage, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,13 @@ import {
|
|||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
AdminRoomMessage,
|
AdminRoomMessage,
|
||||||
BanMessage,
|
BanMessage,
|
||||||
|
BanUserMessage,
|
||||||
BatchToPusherMessage,
|
BatchToPusherMessage,
|
||||||
BatchToPusherRoomMessage,
|
BatchToPusherRoomMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
@ -16,7 +20,9 @@ import {
|
|||||||
QueryJitsiJwtMessage,
|
QueryJitsiJwtMessage,
|
||||||
RefreshRoomPromptMessage,
|
RefreshRoomPromptMessage,
|
||||||
RoomMessage,
|
RoomMessage,
|
||||||
|
SendUserMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
SilentMessage,
|
SilentMessage,
|
||||||
UserMovesMessage,
|
UserMovesMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
@ -100,11 +106,6 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user,
|
user,
|
||||||
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
|
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
|
||||||
);
|
);
|
||||||
} else if (message.hasPlayglobalmessage()) {
|
|
||||||
socketManager.emitPlayGlobalMessage(
|
|
||||||
room,
|
|
||||||
message.getPlayglobalmessage() as PlayGlobalMessage
|
|
||||||
);
|
|
||||||
} else if (message.hasQueryjitsijwtmessage()) {
|
} else if (message.hasQueryjitsijwtmessage()) {
|
||||||
socketManager.handleQueryJitsiJwtMessage(
|
socketManager.handleQueryJitsiJwtMessage(
|
||||||
user,
|
user,
|
||||||
@ -116,16 +117,37 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user,
|
user,
|
||||||
message.getEmotepromptmessage() as EmotePromptMessage
|
message.getEmotepromptmessage() as EmotePromptMessage
|
||||||
);
|
);
|
||||||
|
} else if (message.hasFollowrequestmessage()) {
|
||||||
|
socketManager.handleFollowRequestMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getFollowrequestmessage() as FollowRequestMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasFollowconfirmationmessage()) {
|
||||||
|
socketManager.handleFollowConfirmationMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getFollowconfirmationmessage() as FollowConfirmationMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasFollowabortmessage()) {
|
||||||
|
socketManager.handleFollowAbortMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getFollowabortmessage() as FollowAbortMessage
|
||||||
|
);
|
||||||
} else if (message.hasSendusermessage()) {
|
} else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
if (sendUserMessage !== undefined) {
|
socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage);
|
||||||
socketManager.handlerSendUserMessage(user, sendUserMessage);
|
|
||||||
}
|
|
||||||
} else if (message.hasBanusermessage()) {
|
} else if (message.hasBanusermessage()) {
|
||||||
const banUserMessage = message.getBanusermessage();
|
const banUserMessage = message.getBanusermessage();
|
||||||
if (banUserMessage !== undefined) {
|
socketManager.handlerBanUserMessage(room, user, banUserMessage as BanUserMessage);
|
||||||
socketManager.handlerBanUserMessage(room, user, banUserMessage);
|
} else if (message.hasSetplayerdetailsmessage()) {
|
||||||
}
|
const setPlayerDetailsMessage = message.getSetplayerdetailsmessage();
|
||||||
|
socketManager.handleSetPlayerDetails(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
setPlayerDetailsMessage as SetPlayerDetailsMessage
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unhandled message type");
|
throw new Error("Unhandled message type");
|
||||||
}
|
}
|
||||||
@ -160,7 +182,7 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager
|
socketManager
|
||||||
.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY())
|
.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY())
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
emitErrorOnZoneSocket(call, e.toString());
|
emitErrorOnZoneSocket(call, e);
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on("cancelled", () => {
|
call.on("cancelled", () => {
|
||||||
@ -190,7 +212,7 @@ const roomManager: IRoomManagerServer = {
|
|||||||
const roomMessage = call.request;
|
const roomMessage = call.request;
|
||||||
|
|
||||||
socketManager.addRoomListener(call, roomMessage.getRoomid()).catch((e) => {
|
socketManager.addRoomListener(call, roomMessage.getRoomid()).catch((e) => {
|
||||||
emitErrorOnRoomSocket(call, e.toString());
|
emitErrorOnRoomSocket(call, e);
|
||||||
});
|
});
|
||||||
|
|
||||||
call.on("cancelled", () => {
|
call.on("cancelled", () => {
|
||||||
@ -251,7 +273,12 @@ const roomManager: IRoomManagerServer = {
|
|||||||
},
|
},
|
||||||
sendAdminMessage(call: ServerUnaryCall<AdminMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
sendAdminMessage(call: ServerUnaryCall<AdminMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||||
socketManager
|
socketManager
|
||||||
.sendAdminMessage(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage())
|
.sendAdminMessage(
|
||||||
|
call.request.getRoomid(),
|
||||||
|
call.request.getRecipientuuid(),
|
||||||
|
call.request.getMessage(),
|
||||||
|
call.request.getType()
|
||||||
|
)
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
|
|
||||||
callback(null, new EmptyMessage());
|
callback(null, new EmptyMessage());
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
import { createWriteStream } from "fs";
|
import { createWriteStream } from "fs";
|
||||||
import { join, dirname } from "path";
|
import { join, dirname } from "path";
|
||||||
import Busboy from "busboy";
|
import Busboy from "busboy";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
import { ReadStream } from "fs";
|
import { ReadStream } from "fs";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { MapDetailsData } from "./AdminApi/MapDetailsData";
|
import { isMapDetailsData, MapDetailsData } from "./AdminApi/MapDetailsData";
|
||||||
import { RoomRedirect } from "./AdminApi/RoomRedirect";
|
import { isRoomRedirect, RoomRedirect } from "./AdminApi/RoomRedirect";
|
||||||
|
|
||||||
class AdminApi {
|
class AdminApi {
|
||||||
async fetchMapDetails(playUri: string): Promise<MapDetailsData | RoomRedirect> {
|
async fetchMapDetails(playUri: string): Promise<MapDetailsData | RoomRedirect> {
|
||||||
@ -17,6 +17,12 @@ class AdminApi {
|
|||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isMapDetailsData(res.data) && !isRoomRedirect(res.data)) {
|
||||||
|
console.error("Unexpected answer from the /api/map admin endpoint.", res.data);
|
||||||
|
throw new Error("Unexpected answer from the /api/map admin endpoint.");
|
||||||
|
}
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const EventEmitter = require("events");
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
const clientJoinEvent = "clientJoin";
|
const clientJoinEvent = "clientJoin";
|
||||||
const clientLeaveEvent = "clientLeave";
|
const clientLeaveEvent = "clientLeave";
|
||||||
|
@ -32,7 +32,7 @@ class MapFetcher {
|
|||||||
//throw new Error("Invalid map format for map " + mapUrl);
|
//throw new Error("Invalid map format for map " + mapUrl);
|
||||||
console.error("Invalid map format for map " + mapUrl);
|
console.error("Invalid map format for map " + mapUrl);
|
||||||
}
|
}
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-unsafe-return */
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,19 @@ import {
|
|||||||
import { UserSocket } from "_Model/User";
|
import { UserSocket } from "_Model/User";
|
||||||
import { RoomSocket, ZoneSocket } from "../RoomManager";
|
import { RoomSocket, ZoneSocket } from "../RoomManager";
|
||||||
|
|
||||||
export function emitError(Client: UserSocket, message: string): void {
|
function getMessageFromError(error: unknown): string {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
} else if (typeof error === "string") {
|
||||||
|
return error;
|
||||||
|
} else {
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emitError(Client: UserSocket, error: unknown): void {
|
||||||
|
const message = getMessageFromError(error);
|
||||||
|
|
||||||
const errorMessage = new ErrorMessage();
|
const errorMessage = new ErrorMessage();
|
||||||
errorMessage.setMessage(message);
|
errorMessage.setMessage(message);
|
||||||
|
|
||||||
@ -23,8 +35,9 @@ export function emitError(Client: UserSocket, message: string): void {
|
|||||||
console.warn(message);
|
console.warn(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emitErrorOnRoomSocket(Client: RoomSocket, message: string): void {
|
export function emitErrorOnRoomSocket(Client: RoomSocket, error: unknown): void {
|
||||||
console.error(message);
|
console.error(error);
|
||||||
|
const message = getMessageFromError(error);
|
||||||
|
|
||||||
const errorMessage = new ErrorMessage();
|
const errorMessage = new ErrorMessage();
|
||||||
errorMessage.setMessage(message);
|
errorMessage.setMessage(message);
|
||||||
@ -41,8 +54,9 @@ export function emitErrorOnRoomSocket(Client: RoomSocket, message: string): void
|
|||||||
console.warn(message);
|
console.warn(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emitErrorOnZoneSocket(Client: ZoneSocket, message: string): void {
|
export function emitErrorOnZoneSocket(Client: ZoneSocket, error: unknown): void {
|
||||||
console.error(message);
|
console.error(error);
|
||||||
|
const message = getMessageFromError(error);
|
||||||
|
|
||||||
const errorMessage = new ErrorMessage();
|
const errorMessage = new ErrorMessage();
|
||||||
errorMessage.setMessage(message);
|
errorMessage.setMessage(message);
|
||||||
|
@ -30,9 +30,14 @@ import {
|
|||||||
BanUserMessage,
|
BanUserMessage,
|
||||||
RefreshRoomMessage,
|
RefreshRoomMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
BatchToPusherRoomMessage,
|
BatchToPusherRoomMessage,
|
||||||
SubToPusherRoomMessage,
|
SubToPusherRoomMessage,
|
||||||
|
SetPlayerDetailsMessage,
|
||||||
|
PlayerDetailsUpdatedMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { User, UserSocket } from "../Model/User";
|
import { User, UserSocket } from "../Model/User";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
@ -151,20 +156,9 @@ export class SocketManager {
|
|||||||
//room.setViewport(client, client.viewport);
|
//room.setViewport(client, client.viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useless now, will be useful again if we allow editing details in game
|
handleSetPlayerDetails(room: GameRoom, user: User, playerDetailsMessage: SetPlayerDetailsMessage) {
|
||||||
/*handleSetPlayerDetails(client: UserSocket, playerDetailsMessage: SetPlayerDetailsMessage) {
|
room.updatePlayerDetails(user, playerDetailsMessage);
|
||||||
const playerDetails = {
|
}
|
||||||
name: playerDetailsMessage.getName(),
|
|
||||||
characterLayers: playerDetailsMessage.getCharacterlayersList()
|
|
||||||
};
|
|
||||||
//console.log(SocketIoEvent.SET_PLAYER_DETAILS, playerDetails);
|
|
||||||
if (!isSetPlayerDetailsMessage(playerDetails)) {
|
|
||||||
emitError(client, 'Invalid SET_PLAYER_DETAILS message received: ');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
client.name = playerDetails.name;
|
|
||||||
client.characterLayers = SocketManager.mergeCharacterLayersAndCustomTextures(playerDetails.characterLayers, client.textures);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
handleSilentMessage(room: GameRoom, user: User, silentMessage: SilentMessage) {
|
handleSilentMessage(room: GameRoom, user: User, silentMessage: SilentMessage) {
|
||||||
room.setSilent(user, silentMessage.getSilent());
|
room.setSilent(user, silentMessage.getSilent());
|
||||||
@ -206,7 +200,7 @@ export class SocketManager {
|
|||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
// TODO: only compute credentials if data.signal.type === "offer"
|
// TODO: only compute credentials if data.signal.type === "offer"
|
||||||
if (TURN_STATIC_AUTH_SECRET !== "") {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials(user.id.toString(), TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcSignalToClient.setWebrtcusername(username);
|
webrtcSignalToClient.setWebrtcusername(username);
|
||||||
webrtcSignalToClient.setWebrtcpassword(password);
|
webrtcSignalToClient.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -236,7 +230,7 @@ export class SocketManager {
|
|||||||
webrtcSignalToClient.setSignal(data.getSignal());
|
webrtcSignalToClient.setSignal(data.getSignal());
|
||||||
// TODO: only compute credentials if data.signal.type === "offer"
|
// TODO: only compute credentials if data.signal.type === "offer"
|
||||||
if (TURN_STATIC_AUTH_SECRET !== "") {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials(user.id.toString(), TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcSignalToClient.setWebrtcusername(username);
|
webrtcSignalToClient.setWebrtcusername(username);
|
||||||
webrtcSignalToClient.setWebrtcpassword(password);
|
webrtcSignalToClient.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -282,7 +276,9 @@ export class SocketManager {
|
|||||||
(thing: Movable, newZone: Zone | null, listener: ZoneSocket) =>
|
(thing: Movable, newZone: Zone | null, listener: ZoneSocket) =>
|
||||||
this.onClientLeave(thing, newZone, listener),
|
this.onClientLeave(thing, newZone, listener),
|
||||||
(emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) =>
|
(emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) =>
|
||||||
this.onEmote(emoteEventMessage, listener)
|
this.onEmote(emoteEventMessage, listener),
|
||||||
|
(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ZoneSocket) =>
|
||||||
|
this.onPlayerDetailsUpdated(playerDetailsUpdatedMessage, listener)
|
||||||
)
|
)
|
||||||
.then((gameRoom) => {
|
.then((gameRoom) => {
|
||||||
gaugeManager.incNbRoomGauge();
|
gaugeManager.incNbRoomGauge();
|
||||||
@ -317,7 +313,7 @@ export class SocketManager {
|
|||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
const userJoinedZoneMessage = new UserJoinedZoneMessage();
|
const userJoinedZoneMessage = new UserJoinedZoneMessage();
|
||||||
if (!Number.isInteger(thing.id)) {
|
if (!Number.isInteger(thing.id)) {
|
||||||
throw new Error("clientUser.userId is not an integer " + thing.id);
|
throw new Error(`clientUser.userId is not an integer ${thing.id}`);
|
||||||
}
|
}
|
||||||
userJoinedZoneMessage.setUserid(thing.id);
|
userJoinedZoneMessage.setUserid(thing.id);
|
||||||
userJoinedZoneMessage.setUseruuid(thing.uuid);
|
userJoinedZoneMessage.setUseruuid(thing.uuid);
|
||||||
@ -329,6 +325,12 @@ export class SocketManager {
|
|||||||
userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl);
|
userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl);
|
||||||
}
|
}
|
||||||
userJoinedZoneMessage.setCompanion(thing.companion);
|
userJoinedZoneMessage.setCompanion(thing.companion);
|
||||||
|
if (thing.outlineColor === undefined) {
|
||||||
|
userJoinedZoneMessage.setHasoutline(false);
|
||||||
|
} else {
|
||||||
|
userJoinedZoneMessage.setHasoutline(true);
|
||||||
|
userJoinedZoneMessage.setOutlinecolor(thing.outlineColor);
|
||||||
|
}
|
||||||
|
|
||||||
const subMessage = new SubToPusherMessage();
|
const subMessage = new SubToPusherMessage();
|
||||||
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
|
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
|
||||||
@ -378,6 +380,13 @@ export class SocketManager {
|
|||||||
emitZoneMessage(subMessage, client);
|
emitZoneMessage(subMessage, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, client: ZoneSocket) {
|
||||||
|
const subMessage = new SubToPusherMessage();
|
||||||
|
subMessage.setPlayerdetailsupdatedmessage(playerDetailsUpdatedMessage);
|
||||||
|
|
||||||
|
emitZoneMessage(subMessage, client);
|
||||||
|
}
|
||||||
|
|
||||||
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone | null, group: Group): void {
|
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone | null, group: Group): void {
|
||||||
const position = group.getPosition();
|
const position = group.getPosition();
|
||||||
const pointMessage = new PointMessage();
|
const pointMessage = new PointMessage();
|
||||||
@ -440,7 +449,10 @@ export class SocketManager {
|
|||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
webrtcStartMessage1.setInitiator(true);
|
webrtcStartMessage1.setInitiator(true);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== "") {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials(
|
||||||
|
otherUser.id.toString(),
|
||||||
|
TURN_STATIC_AUTH_SECRET
|
||||||
|
);
|
||||||
webrtcStartMessage1.setWebrtcusername(username);
|
webrtcStartMessage1.setWebrtcusername(username);
|
||||||
webrtcStartMessage1.setWebrtcpassword(password);
|
webrtcStartMessage1.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -454,7 +466,7 @@ export class SocketManager {
|
|||||||
webrtcStartMessage2.setUserid(user.id);
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
webrtcStartMessage2.setInitiator(false);
|
webrtcStartMessage2.setInitiator(false);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== "") {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials(user.id.toString(), TURN_STATIC_AUTH_SECRET);
|
||||||
webrtcStartMessage2.setWebrtcusername(username);
|
webrtcStartMessage2.setWebrtcusername(username);
|
||||||
webrtcStartMessage2.setWebrtcpassword(password);
|
webrtcStartMessage2.setWebrtcpassword(password);
|
||||||
}
|
}
|
||||||
@ -478,7 +490,7 @@ export class SocketManager {
|
|||||||
hmac.setEncoding("base64");
|
hmac.setEncoding("base64");
|
||||||
hmac.write(username);
|
hmac.write(username);
|
||||||
hmac.end();
|
hmac.end();
|
||||||
const password = hmac.read();
|
const password = hmac.read() as string;
|
||||||
return {
|
return {
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
@ -519,15 +531,6 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitPlayGlobalMessage(room: GameRoom, playGlobalMessage: PlayGlobalMessage) {
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
|
||||||
serverToClientMessage.setPlayglobalmessage(playGlobalMessage);
|
|
||||||
|
|
||||||
for (const [id, user] of room.getUsers().entries()) {
|
|
||||||
user.socket.write(serverToClientMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorlds(): Map<string, PromiseLike<GameRoom>> {
|
public getWorlds(): Map<string, PromiseLike<GameRoom>> {
|
||||||
return this.roomsPromises;
|
return this.roomsPromises;
|
||||||
}
|
}
|
||||||
@ -572,7 +575,7 @@ export class SocketManager {
|
|||||||
user.socket.write(serverToClientMessage);
|
user.socket.write(serverToClientMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) {
|
public handleSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
sendUserMessage.setMessage(sendUserMessageToSend.getMessage());
|
||||||
sendUserMessage.setType(sendUserMessageToSend.getType());
|
sendUserMessage.setType(sendUserMessageToSend.getType());
|
||||||
@ -691,7 +694,7 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendAdminMessage(roomId: string, recipientUuid: string, message: string): Promise<void> {
|
public async sendAdminMessage(roomId: string, recipientUuid: string, message: string, type: string): Promise<void> {
|
||||||
const room = await this.roomsPromises.get(roomId);
|
const room = await this.roomsPromises.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error(
|
console.error(
|
||||||
@ -715,7 +718,7 @@ export class SocketManager {
|
|||||||
for (const recipient of recipients) {
|
for (const recipient of recipients) {
|
||||||
const sendUserMessage = new SendUserMessage();
|
const sendUserMessage = new SendUserMessage();
|
||||||
sendUserMessage.setMessage(message);
|
sendUserMessage.setMessage(message);
|
||||||
sendUserMessage.setType("ban"); //todo: is the type correct?
|
sendUserMessage.setType(type);
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
serverToClientMessage.setSendusermessage(sendUserMessage);
|
serverToClientMessage.setSendusermessage(sendUserMessage);
|
||||||
@ -833,6 +836,39 @@ export class SocketManager {
|
|||||||
emoteEventMessage.setActoruserid(user.id);
|
emoteEventMessage.setActoruserid(user.id);
|
||||||
room.emitEmoteEvent(user, emoteEventMessage);
|
room.emitEmoteEvent(user, emoteEventMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFollowRequestMessage(room: GameRoom, user: User, message: FollowRequestMessage) {
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setFollowrequestmessage(message);
|
||||||
|
room.sendToOthersInGroupIncludingUser(user, clientMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowConfirmationMessage(room: GameRoom, user: User, message: FollowConfirmationMessage) {
|
||||||
|
const leader = room.getUserById(message.getLeader());
|
||||||
|
if (!leader) {
|
||||||
|
const message = `Could not follow user "{message.getLeader()}" in room "{room.roomUrl}".`;
|
||||||
|
console.info(message, "Maybe the user just left.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By security, we look at the group leader. If the group leader is NOT the leader in the message,
|
||||||
|
// everybody should stop following the group leader (to avoid having 2 group leaders)
|
||||||
|
if (user?.group?.leader && user?.group?.leader !== leader) {
|
||||||
|
user?.group?.leader?.stopLeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
leader.addFollower(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) {
|
||||||
|
if (user.id === message.getLeader()) {
|
||||||
|
user?.group?.leader?.stopLeading();
|
||||||
|
} else {
|
||||||
|
// Forward message
|
||||||
|
const leader = room.getUserById(message.getLeader());
|
||||||
|
leader?.delFollower(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -101,11 +101,11 @@ export class VariablesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We store a copy of the object (to make it immutable)
|
// We store a copy of the object (to make it immutable)
|
||||||
objects.set(object.name, this.iTiledObjectToVariable(object));
|
objects.set(object.name as string, this.iTiledObjectToVariable(object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (layer.type === "group") {
|
} else if (layer.type === "group") {
|
||||||
for (const innerLayer of layer.layers) {
|
for (const innerLayer of layer.layers as ITiledMapLayer[]) {
|
||||||
this.recursiveFindVariablesInLayer(innerLayer, objects);
|
this.recursiveFindVariablesInLayer(innerLayer, objects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ export class VariablesManager {
|
|||||||
|
|
||||||
if (object.properties) {
|
if (object.properties) {
|
||||||
for (const property of object.properties) {
|
for (const property of object.properties) {
|
||||||
const value = property.value;
|
const value = property.value as unknown;
|
||||||
switch (property.name) {
|
switch (property.name) {
|
||||||
case "default":
|
case "default":
|
||||||
variable.defaultValue = JSON.stringify(value);
|
variable.defaultValue = JSON.stringify(value);
|
||||||
|
@ -51,7 +51,8 @@ describe("GameRoom", () => {
|
|||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
emote
|
emote,
|
||||||
|
() => {}
|
||||||
);
|
);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
|
||||||
@ -86,7 +87,8 @@ describe("GameRoom", () => {
|
|||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
emote
|
emote,
|
||||||
|
() => {}
|
||||||
);
|
);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
|
||||||
@ -125,7 +127,8 @@ describe("GameRoom", () => {
|
|||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
emote
|
emote,
|
||||||
|
() => {}
|
||||||
);
|
);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage("1", 100, 100));
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import "jasmine";
|
import "jasmine";
|
||||||
import {PositionNotifier} from "../src/Model/PositionNotifier";
|
import { PositionNotifier } from "../src/Model/PositionNotifier";
|
||||||
import {User, UserSocket} from "../src/Model/User";
|
import { User, UserSocket } from "../src/Model/User";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../src/RoomManager";
|
import { ZoneSocket } from "../src/RoomManager";
|
||||||
|
|
||||||
|
|
||||||
describe("PositionNotifier", () => {
|
describe("PositionNotifier", () => {
|
||||||
it("should receive notifications when player moves", () => {
|
it("should receive notifications when player moves", () => {
|
||||||
@ -13,27 +12,59 @@ describe("PositionNotifier", () => {
|
|||||||
let moveTriggered = false;
|
let moveTriggered = false;
|
||||||
let leaveTriggered = false;
|
let leaveTriggered = false;
|
||||||
|
|
||||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => {
|
const positionNotifier = new PositionNotifier(
|
||||||
enterTriggered = true;
|
300,
|
||||||
}, (thing: Movable, position: PositionInterface) => {
|
300,
|
||||||
moveTriggered = true;
|
(thing: Movable) => {
|
||||||
}, (thing: Movable) => {
|
enterTriggered = true;
|
||||||
leaveTriggered = true;
|
},
|
||||||
}, () => {});
|
(thing: Movable, position: PositionInterface) => {
|
||||||
|
moveTriggered = true;
|
||||||
|
},
|
||||||
|
(thing: Movable) => {
|
||||||
|
leaveTriggered = true;
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(
|
||||||
x: 500,
|
1,
|
||||||
y: 500,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: 500,
|
||||||
|
y: 500,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
const user2 = new User(
|
||||||
x: -9999,
|
2,
|
||||||
y: -9999,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: -9999,
|
||||||
|
y: -9999,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
||||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
||||||
@ -46,21 +77,21 @@ describe("PositionNotifier", () => {
|
|||||||
bottom: 500
|
bottom: 500
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
|
|
||||||
// Move inside the zone
|
// Move inside the zone
|
||||||
user2.setPosition({x:501, y:500, direction: 'down', moving: false});
|
user2.setPosition({ x: 501, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(false);
|
expect(enterTriggered).toBe(false);
|
||||||
expect(moveTriggered).toBe(true);
|
expect(moveTriggered).toBe(true);
|
||||||
moveTriggered = false;
|
moveTriggered = false;
|
||||||
|
|
||||||
// Move out of the zone in a zone that we don't track
|
// Move out of the zone in a zone that we don't track
|
||||||
user2.setPosition({x: 901, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 901, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(false);
|
expect(enterTriggered).toBe(false);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
@ -68,7 +99,7 @@ describe("PositionNotifier", () => {
|
|||||||
leaveTriggered = false;
|
leaveTriggered = false;
|
||||||
|
|
||||||
// Move back in
|
// Move back in
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
expect(leaveTriggered).toBe(false);
|
expect(leaveTriggered).toBe(false);
|
||||||
@ -88,27 +119,59 @@ describe("PositionNotifier", () => {
|
|||||||
let moveTriggered = false;
|
let moveTriggered = false;
|
||||||
let leaveTriggered = false;
|
let leaveTriggered = false;
|
||||||
|
|
||||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable, fromZone: Zone|null ) => {
|
const positionNotifier = new PositionNotifier(
|
||||||
enterTriggered = true;
|
300,
|
||||||
}, (thing: Movable, position: PositionInterface) => {
|
300,
|
||||||
moveTriggered = true;
|
(thing: Movable, fromZone: Zone | null) => {
|
||||||
}, (thing: Movable) => {
|
enterTriggered = true;
|
||||||
leaveTriggered = true;
|
},
|
||||||
}, () => {});
|
(thing: Movable, position: PositionInterface) => {
|
||||||
|
moveTriggered = true;
|
||||||
|
},
|
||||||
|
(thing: Movable) => {
|
||||||
|
leaveTriggered = true;
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(
|
||||||
x: 500,
|
1,
|
||||||
y: 500,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: 500,
|
||||||
|
y: 500,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
const user2 = new User(
|
||||||
x: 0,
|
2,
|
||||||
y: 0,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const listener = {} as ZoneSocket;
|
const listener = {} as ZoneSocket;
|
||||||
positionNotifier.addZoneListener(listener, 0, 0);
|
positionNotifier.addZoneListener(listener, 0, 0);
|
||||||
@ -124,14 +187,12 @@ describe("PositionNotifier", () => {
|
|||||||
positionNotifier.enter(user1);
|
positionNotifier.enter(user1);
|
||||||
positionNotifier.enter(user2);
|
positionNotifier.enter(user2);
|
||||||
|
|
||||||
|
|
||||||
//expect(newUsers.length).toBe(2);
|
//expect(newUsers.length).toBe(2);
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
|
|
||||||
|
|
||||||
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
@ -182,4 +243,4 @@ describe("PositionNotifier", () => {
|
|||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
//expect(newUsers.length).toBe(2);
|
//expect(newUsers.length).toBe(2);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
2668
back/yarn.lock
@ -73,7 +73,7 @@ services:
|
|||||||
DEBUG: "socket:*"
|
DEBUG: "socket:*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
# wait for files generated by "messages" container to exists
|
# wait for files generated by "messages" container to exists
|
||||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
STARTUP_COMMAND_2: sleep 5; while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
@ -132,7 +132,7 @@ services:
|
|||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
# wait for files generated by "messages" container to exists
|
# wait for files generated by "messages" container to exists
|
||||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
STARTUP_COMMAND_2: sleep 5; while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
ALLOW_ARTILLERY: "true"
|
ALLOW_ARTILLERY: "true"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
### Listen to camera updates
|
### Listen to camera updates
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void
|
WA.camera.onCameraUpdate(): Subscription
|
||||||
```
|
```
|
||||||
|
|
||||||
Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent.
|
Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent.
|
||||||
@ -19,5 +19,6 @@ The event has the following attributes :
|
|||||||
|
|
||||||
Example :
|
Example :
|
||||||
```javascript
|
```javascript
|
||||||
WA.camera.onCameraUpdate((worldView) => console.log(worldView));
|
const subscription = WA.camera.onCameraUpdate().subscribe((worldView) => console.log(worldView));
|
||||||
```
|
//later...
|
||||||
|
subscription.unsubscribe();
|
||||||
|
@ -107,6 +107,27 @@ WA.onInit().then(() => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Get the position of the player
|
||||||
|
```
|
||||||
|
WA.player.getPosition(): Promise<Position>
|
||||||
|
```
|
||||||
|
The player's current position is available using the `WA.player.getPosition()` function.
|
||||||
|
|
||||||
|
`Position` has the following attributes :
|
||||||
|
* **x (number) :** The coordinate x of the current player's position.
|
||||||
|
* **y (number) :** The coordinate y of the current player's position.
|
||||||
|
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
You need to wait for the end of the initialization before calling `WA.player.getPosition()`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
WA.onInit().then(async () => {
|
||||||
|
console.log('Position: ', await WA.player.getPosition());
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Listen to player movement
|
### Listen to player movement
|
||||||
```
|
```
|
||||||
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
||||||
@ -151,3 +172,25 @@ Example:
|
|||||||
```javascript
|
```javascript
|
||||||
WA.player.state.toto //will retrieve the variable
|
WA.player.state.toto //will retrieve the variable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Set the outline color of the player
|
||||||
|
```
|
||||||
|
WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>;
|
||||||
|
WA.player.removeOutlineColor(): Promise<void>;
|
||||||
|
```
|
||||||
|
|
||||||
|
You can display a thin line around your player's name (the "outline").
|
||||||
|
|
||||||
|
Use `setOutlineColor` to set the outline and `removeOutlineColor` to remove it.
|
||||||
|
|
||||||
|
Colors are expressed in RGB. Each parameter is an integer between 0 and 255.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Let's add a red outline to our player
|
||||||
|
WA.player.setOutlineColor(255, 0, 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
When you set the outline on your player, other players will see the outline too (the outline color is shared across
|
||||||
|
browsers automatically).
|
||||||
|
|
||||||
|
![](images/outlines.png)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
{.section-title.accent.text-primary}
|
{.section-title.accent.text-primary}
|
||||||
|
|
||||||
# API Room functions Reference
|
# API Room functions Reference
|
||||||
|
|
||||||
### Working with group layers
|
### Working with group layers
|
||||||
If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together.
|
|
||||||
|
If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names
|
||||||
|
together.
|
||||||
|
|
||||||
Example :
|
Example :
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -12,6 +15,7 @@ Example :
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
The name of the layers of this map are :
|
The name of the layers of this map are :
|
||||||
|
|
||||||
* `entries/start`
|
* `entries/start`
|
||||||
* `bottom/ground/under`
|
* `bottom/ground/under`
|
||||||
* `bottom/build/carpet`
|
* `bottom/build/carpet`
|
||||||
@ -26,29 +30,32 @@ WA.room.onLeaveLayer(name: string): Subscription
|
|||||||
|
|
||||||
Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
|
Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
|
||||||
|
|
||||||
* **name**: the name of the layer who as defined in Tiled.
|
* **name**: the name of the layer who as defined in Tiled.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.onEnterLayer('myLayer').subscribe(() => {
|
WA.room.onEnterLayer('myLayer').subscribe(() => {
|
||||||
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
|
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
|
||||||
});
|
});
|
||||||
|
|
||||||
WA.room.onLeaveLayer('myLayer').subscribe(() => {
|
WA.room.onLeaveLayer('myLayer').subscribe(() => {
|
||||||
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
|
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Show / Hide a layer
|
### Show / Hide a layer
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.room.showLayer(layerName : string): void
|
WA.room.showLayer(layerName : string): void
|
||||||
WA.room.hideLayer(layerName : string) : void
|
WA.room.hideLayer(layerName : string) : void
|
||||||
```
|
```
|
||||||
These 2 methods can be used to show and hide a layer.
|
|
||||||
if `layerName` is the name of a group layer, show/hide all the layer in that group layer.
|
These 2 methods can be used to show and hide a layer. if `layerName` is the name of a group layer, show/hide all the
|
||||||
|
layer in that group layer.
|
||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.showLayer('bottom');
|
WA.room.showLayer('bottom');
|
||||||
//...
|
//...
|
||||||
@ -61,12 +68,14 @@ WA.room.hideLayer('bottom');
|
|||||||
WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void;
|
WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void;
|
||||||
```
|
```
|
||||||
|
|
||||||
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`.
|
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist,
|
||||||
|
create the property `propertyName` and set the value of the property at `propertyValue`.
|
||||||
|
|
||||||
Note :
|
Note :
|
||||||
To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
|
To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
|
||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/');
|
WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/');
|
||||||
```
|
```
|
||||||
@ -79,13 +88,12 @@ WA.room.id: string;
|
|||||||
|
|
||||||
The ID of the current room is available from the `WA.room.id` property.
|
The ID of the current room is available from the `WA.room.id` property.
|
||||||
|
|
||||||
{.alert.alert-info}
|
{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.id`
|
||||||
You need to wait for the end of the initialization before accessing `WA.room.id`
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
WA.onInit().then(() => {
|
WA.onInit().then(() => {
|
||||||
console.log('Room id: ', WA.room.id);
|
console.log('Room id: ', WA.room.id);
|
||||||
// Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json"
|
// Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json"
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -97,19 +105,17 @@ WA.room.mapURL: string;
|
|||||||
|
|
||||||
The URL of the map is available from the `WA.room.mapURL` property.
|
The URL of the map is available from the `WA.room.mapURL` property.
|
||||||
|
|
||||||
{.alert.alert-info}
|
{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.mapURL`
|
||||||
You need to wait for the end of the initialization before accessing `WA.room.mapURL`
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
WA.onInit().then(() => {
|
WA.onInit().then(() => {
|
||||||
console.log('Map URL: ', WA.room.mapURL);
|
console.log('Map URL: ', WA.room.mapURL);
|
||||||
// Will output something like: 'https://mymap.org/map.json"
|
// Will output something like: 'https://mymap.org/map.json"
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Getting map data
|
### Getting map data
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.room.getTiledMap(): Promise<ITiledMap>
|
WA.room.getTiledMap(): Promise<ITiledMap>
|
||||||
```
|
```
|
||||||
@ -121,12 +127,16 @@ const map = await WA.room.getTiledMap();
|
|||||||
console.log("Map generated with Tiled version ", map.tiledversion);
|
console.log("Map generated with Tiled version ", map.tiledversion);
|
||||||
```
|
```
|
||||||
|
|
||||||
Check the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/).
|
Check
|
||||||
|
the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/)
|
||||||
|
.
|
||||||
|
|
||||||
### Changing tiles
|
### Changing tiles
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.room.setTiles(tiles: TileDescriptor[]): void
|
WA.room.setTiles(tiles: TileDescriptor[]): void
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
|
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
|
||||||
|
|
||||||
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
||||||
@ -137,43 +147,48 @@ If `tile` is a string, it's not the id of the tile but the value of the property
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
`TileDescriptor` has the following attributes :
|
`TileDescriptor` has the following attributes :
|
||||||
|
|
||||||
* **x (number) :** The coordinate x of the tile that you want to replace.
|
* **x (number) :** The coordinate x of the tile that you want to replace.
|
||||||
* **y (number) :** The coordinate y of the tile that you want to replace.
|
* **y (number) :** The coordinate y of the tile that you want to replace.
|
||||||
* **tile (number | string) :** The id of the tile that will be placed in the map.
|
* **tile (number | string) :** The id of the tile that will be placed in the map.
|
||||||
* **layer (string) :** The name of the layer where the tile will be placed.
|
* **layer (string) :** The name of the layer where the tile will be placed.
|
||||||
|
|
||||||
**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor.
|
**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want
|
||||||
|
to the id of the tile in Tiled Editor.
|
||||||
|
|
||||||
Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
|
Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
|
||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.setTiles([
|
WA.room.setTiles([
|
||||||
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
|
{ x: 6, y: 4, tile: 'blue', layer: 'setTiles' },
|
||||||
{x: 7, y: 4, tile: 109, layer: 'setTiles'},
|
{ x: 7, y: 4, tile: 109, layer: 'setTiles' },
|
||||||
{x: 8, y: 4, tile: 109, layer: 'setTiles'},
|
{ x: 8, y: 4, tile: 109, layer: 'setTiles' },
|
||||||
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
{ x: 9, y: 4, tile: 'blue', layer: 'setTiles' }
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Loading a tileset
|
### Loading a tileset
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.room.loadTileset(url: string): Promise<number>
|
WA.room.loadTileset(url: string): Promise<number>
|
||||||
```
|
```
|
||||||
|
|
||||||
Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset.
|
Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset.
|
||||||
|
|
||||||
You can create a tileset file in Tile Editor.
|
You can create a tileset file in Tile Editor.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
||||||
WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]);
|
WA.room.setTiles([{ x: 4, y: 4, tile: firstId, layer: 'bottom' }]);
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Embedding websites in a map
|
## Embedding websites in a map
|
||||||
|
|
||||||
You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using the ["website" objects](website-in-map.md)).
|
You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using
|
||||||
|
the ["website" objects](website-in-map.md)).
|
||||||
|
|
||||||
### Getting an instance of a website already embedded in the map
|
### Getting an instance of a website already embedded in the map
|
||||||
|
|
||||||
@ -181,8 +196,8 @@ You can use the scripting API to embed websites in a map, or to edit websites th
|
|||||||
WA.room.website.get(objectName: string): Promise<EmbeddedWebsite>
|
WA.room.website.get(objectName: string): Promise<EmbeddedWebsite>
|
||||||
```
|
```
|
||||||
|
|
||||||
You can get an instance of an embedded website by using the `WA.room.website.get()` method.
|
You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of
|
||||||
It returns a promise of an `EmbeddedWebsite` instance.
|
an `EmbeddedWebsite` instance.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Get an existing website object where 'my_website' is the name of the object (on any layer object of the map)
|
// Get an existing website object where 'my_website' is the name of the object (on any layer object of the map)
|
||||||
@ -191,7 +206,6 @@ website.url = 'https://example.com';
|
|||||||
website.visible = true;
|
website.visible = true;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Adding a new website in a map
|
### Adding a new website in a map
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -201,34 +215,38 @@ interface CreateEmbeddedWebsiteEvent {
|
|||||||
name: string; // A unique name for this iframe
|
name: string; // A unique name for this iframe
|
||||||
url: string; // The URL the iframe points to.
|
url: string; // The URL the iframe points to.
|
||||||
position: {
|
position: {
|
||||||
x: number, // In pixels, relative to the map coordinates
|
x: number, // In "game" pixels, relative to the map or player coordinates, depending on origin
|
||||||
y: number, // In pixels, relative to the map coordinates
|
y: number, // In "game" pixels, relative to the map or player coordinates, depending on origin
|
||||||
width: number, // In pixels, sensitive to zoom level
|
width: number, // In "game" pixels
|
||||||
height: number, // In pixels, sensitive to zoom level
|
height: number, // In "game" pixels
|
||||||
},
|
},
|
||||||
visible?: boolean, // Whether to display the iframe or not
|
visible?: boolean, // Whether to display the iframe or not
|
||||||
allowApi?: boolean, // Whether the scripting API should be available to the iframe
|
allowApi?: boolean, // Whether the scripting API should be available to the iframe
|
||||||
allow?: string, // The list of feature policies allowed
|
allow?: string, // The list of feature policies allowed
|
||||||
|
origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner, defaults to "map"
|
||||||
|
scale: number, // A ratio used to resize the iframe
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can create an instance of an embedded website by using the `WA.room.website.create()` method.
|
You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns
|
||||||
It returns an `EmbeddedWebsite` instance.
|
an `EmbeddedWebsite` instance.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Create a new website object
|
// Create a new website object
|
||||||
const website = WA.room.website.create({
|
const website = WA.room.website.create({
|
||||||
name: "my_website",
|
name: "my_website",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
position: {
|
position: {
|
||||||
x: 64,
|
x: 64,
|
||||||
y: 128,
|
y: 128,
|
||||||
width: 320,
|
width: 320,
|
||||||
height: 240,
|
height: 240,
|
||||||
},
|
},
|
||||||
visible: true,
|
visible: true,
|
||||||
allowApi: true,
|
allowApi: true,
|
||||||
allow: "fullscreen",
|
allow: "fullscreen",
|
||||||
|
origin: "map",
|
||||||
|
scale: 1,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -240,30 +258,28 @@ WA.room.website.delete(name: string): Promise<void>
|
|||||||
|
|
||||||
Use `WA.room.website.delete` to completely remove an embedded website from your map.
|
Use `WA.room.website.delete` to completely remove an embedded website from your map.
|
||||||
|
|
||||||
|
|
||||||
### The EmbeddedWebsite class
|
### The EmbeddedWebsite class
|
||||||
|
|
||||||
Instances of the `EmbeddedWebsite` class represent the website displayed on the map.
|
Instances of the `EmbeddedWebsite` class represent the website displayed on the map.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class EmbeddedWebsite {
|
class EmbeddedWebsite {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
url: string;
|
url: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
allow: string;
|
allow: string;
|
||||||
allowApi: boolean;
|
allowApi: boolean;
|
||||||
x: number; // In pixels, relative to the map coordinates
|
x: number; // In "game" pixels, relative to the map or player coordinates, depending on origin
|
||||||
y: number; // In pixels, relative to the map coordinates
|
y: number; // In "game" pixels, relative to the map or player coordinates, depending on origin
|
||||||
width: number; // In pixels, sensitive to zoom level
|
width: number; // In "game" pixels
|
||||||
height: number; // In pixels, sensitive to zoom level
|
height: number; // In "game" pixels
|
||||||
|
origin: "player" | "map";
|
||||||
|
scale: number;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When you modify a property of an `EmbeddedWebsite` instance, the iframe is automatically modified in the map.
|
When you modify a property of an `EmbeddedWebsite` instance, the iframe is automatically modified in the map.
|
||||||
|
|
||||||
|
{.alert.alert-warning} The websites you add/edit/delete via the scripting API are only shown locally. If you want them
|
||||||
{.alert.alert-warning}
|
to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users.
|
||||||
The websites you add/edit/delete via the scripting API are only shown locally. If you want them
|
|
||||||
to be displayed for every player, you can use [variables](api-start.md) to share a common state
|
|
||||||
between all users.
|
|
||||||
|
|
||||||
|
@ -29,22 +29,28 @@ It is possible to define special regions on the map that can make the camera zoo
|
|||||||
<img class="document-img" src="images/camera/3_define_new_zone.png" alt="" />
|
<img class="document-img" src="images/camera/3_define_new_zone.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
4. Edit this new object and click on **Add Property**, like this:
|
4. Make sure your object is of type "zone"!
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="images/camera/4_click_add_property.png" alt="" />
|
<img class="document-img" src="images/camera/4_add_zone_type.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
5. Add a **bool** property of name *focusable*:
|
5. Edit this new object and click on **Add Property**, like this:
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="images/camera/5_add_focusable_prop.png" alt="" />
|
<img class="document-img" src="images/camera/5_click_add_property.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
6. Make sure it's checked! :)
|
6. Add a **bool** property of name *focusable*:
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="images/camera/6_make_sure_checked.png" alt="" />
|
<img class="document-img" src="images/camera/6_add_focusable_prop.png" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
7. Make sure it's checked! :)
|
||||||
|
|
||||||
|
<div class="px-5 card rounded d-inline-block">
|
||||||
|
<img class="document-img" src="images/camera/7_make_sure_checked.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
All should be set up now and your new **Focusable Zone** should be working fine!
|
All should be set up now and your new **Focusable Zone** should be working fine!
|
||||||
@ -56,19 +62,19 @@ If you want, you can add an additional property to control how much should the c
|
|||||||
1. Like before, click on **Add Property**
|
1. Like before, click on **Add Property**
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="images/camera/4_click_add_property.png" alt="" />
|
<img class="document-img" src="images/camera/5_click_add_property.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
2. Add a **float** property of name *zoom_margin*:
|
2. Add a **float** property of name *zoom_margin*:
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="images/camera/7_add_zoom_margin.png" alt="" />
|
<img class="document-img" src="images/camera/8_add_zoom_margin.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
2. Define how much (in percentage value) should the zoom be decreased:
|
2. Define how much (in percentage value) should the zoom be decreased:
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="images/camera/8_optional_zoom_margin_defined.png" alt="" />
|
<img class="document-img" src="images/camera/9_optional_zoom_margin_defined.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
For example, if you define your zone as a 300x200 rectangle, setting this property to 0.5 *(50%)* means the camera will try to fit within the viewport the entire zone + margin of 50% of its dimensions, so 450x300.
|
For example, if you define your zone as a 300x200 rectangle, setting this property to 0.5 *(50%)* means the camera will try to fit within the viewport the entire zone + margin of 50% of its dimensions, so 450x300.
|
||||||
|
BIN
docs/maps/images/camera/4_add_zone_type.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@ -60,7 +60,7 @@ WA.chat.sendChatMessage('Hello world', 'Mr Robot');
|
|||||||
|
|
||||||
The `WA` objects contains a number of useful methods enabling you to interact with the WorkAdventure game. For instance, `WA.chat.sendChatMessage` opens the chat and adds a message in it.
|
The `WA` objects contains a number of useful methods enabling you to interact with the WorkAdventure game. For instance, `WA.chat.sendChatMessage` opens the chat and adds a message in it.
|
||||||
|
|
||||||
In your browser console, when you open the map, the chat message should be displayed right away.
|
The message should be displayed in the chat history as soon as you enter the room.
|
||||||
|
|
||||||
## Adding a script in an iFrame
|
## Adding a script in an iFrame
|
||||||
|
|
||||||
|
@ -98,13 +98,14 @@ The exception is the "collides" property that can only be set on tiles, but not
|
|||||||
By setting properties on the map itself, you can help visitors know more about the creators of the map.
|
By setting properties on the map itself, you can help visitors know more about the creators of the map.
|
||||||
|
|
||||||
The following *map* properties are supported:
|
The following *map* properties are supported:
|
||||||
* `mapName` (string)
|
* `mapName` (string): The name of your map
|
||||||
* `mapDescription` (string)
|
* `mapLink` (string): A link to your map, for example a repository
|
||||||
* `mapCopyright` (string)
|
* `mapDescription` (string): A short description of your map
|
||||||
|
* `mapCopyright` (string): Copyright notice
|
||||||
|
|
||||||
And *each tileset* can also have a property called `tilesetCopyright` (string).
|
Each *tileset* can also have a property called `tilesetCopyright` (string).
|
||||||
|
If you are using audio files in your map, you can declare a layer property `audioCopyright` (string).
|
||||||
|
|
||||||
Resulting in a "credit" page in the menu looking like this:
|
Resulting in a "credit" page in the menu looking like this:
|
||||||
|
|
||||||
![](images/mapProperties.png){.document-img}
|
![](images/mapProperties.png){.document-img}
|
||||||
|
|
||||||
|
@ -34,14 +34,15 @@ module.exports = {
|
|||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"no-throw-literal": "error",
|
||||||
// TODO: remove those ignored rules and write a stronger code!
|
// TODO: remove those ignored rules and write a stronger code!
|
||||||
"@typescript-eslint/no-floating-promises": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
"@typescript-eslint/restrict-plus-operands": "off",
|
"@typescript-eslint/restrict-plus-operands": "off",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
"@typescript-eslint/restrict-template-expressions": "off"
|
"@typescript-eslint/restrict-template-expressions": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"svelte3/typescript": true,
|
"svelte3/typescript": true,
|
||||||
|
@ -1 +1,2 @@
|
|||||||
src/Messages/generated
|
src/Messages/generated
|
||||||
|
src/Messages/JsonMessages
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY messages .
|
COPY messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn ts-proto
|
||||||
|
|
||||||
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
|
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
|
||||||
FROM thecodingmachine/nodejs:14-apache
|
FROM thecodingmachine/nodejs:14-apache
|
||||||
|
|
||||||
COPY --chown=docker:docker front .
|
COPY --chown=docker:docker front .
|
||||||
COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated
|
COPY --from=builder --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated
|
||||||
|
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts
|
||||||
|
COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages
|
||||||
|
|
||||||
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
||||||
# iframe.html is only in dev mode to circumvent a limitation
|
# iframe.html is only in dev mode to circumvent a limitation
|
||||||
|
2
front/dist/.htaccess
vendored
@ -20,7 +20,7 @@ RewriteBase /
|
|||||||
# We only want to let Apache serve files and not directories.
|
# We only want to let Apache serve files and not directories.
|
||||||
# Rewrite all other queries starting with _ to index.ts.
|
# Rewrite all other queries starting with _ to index.ts.
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
RewriteRule "^[_@]/" "/index.html" [L]
|
RewriteRule "^[_@*]/" "/index.html" [L]
|
||||||
RewriteRule "^register/" "/index.html" [L]
|
RewriteRule "^register/" "/index.html" [L]
|
||||||
RewriteRule "^login" "/index.html" [L]
|
RewriteRule "^login" "/index.html" [L]
|
||||||
RewriteRule "^jwt" "/index.html" [L]
|
RewriteRule "^jwt" "/index.html" [L]
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
"@types/quill": "^1.3.7",
|
"@types/quill": "^1.3.7",
|
||||||
"@types/uuidv4": "^5.0.0",
|
"@types/uuidv4": "^5.0.0",
|
||||||
"@types/webpack-dev-server": "^3.11.4",
|
"@types/webpack-dev-server": "^3.11.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||||
"@typescript-eslint/parser": "^4.23.0",
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^5.2.4",
|
||||||
"eslint": "^7.26.0",
|
"eslint": "^8.4.1",
|
||||||
"eslint-plugin-svelte3": "^3.2.1",
|
"eslint-plugin-svelte3": "^3.2.1",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.2.9",
|
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.3.1",
|
||||||
"jasmine": "^3.5.0",
|
"jasmine": "^3.5.0",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
@ -32,10 +32,10 @@
|
|||||||
"svelte-check": "^2.1.0",
|
"svelte-check": "^2.1.0",
|
||||||
"svelte-loader": "^3.1.1",
|
"svelte-loader": "^3.1.1",
|
||||||
"svelte-preprocess": "^4.7.3",
|
"svelte-preprocess": "^4.7.3",
|
||||||
"ts-loader": "^9.1.2",
|
"ts-loader": "^9.2.6",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^10.4.0",
|
||||||
"tsconfig-paths": "^3.9.0",
|
"tsconfig-paths": "^3.9.0",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.5.3",
|
||||||
"webpack": "^5.37.0",
|
"webpack": "^5.37.0",
|
||||||
"webpack-cli": "^4.7.0",
|
"webpack-cli": "^4.7.0",
|
||||||
"webpack-dev-server": "^3.11.2"
|
"webpack-dev-server": "^3.11.2"
|
||||||
@ -47,6 +47,7 @@
|
|||||||
"@types/socket.io-client": "^1.4.32",
|
"@types/socket.io-client": "^1.4.32",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"deep-copy-ts": "^0.5.0",
|
||||||
"generic-type-guard": "^3.2.0",
|
"generic-type-guard": "^3.2.0",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"phaser": "^3.54.0",
|
"phaser": "^3.54.0",
|
||||||
@ -61,6 +62,7 @@
|
|||||||
"simple-peer": "^9.11.0",
|
"simple-peer": "^9.11.0",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"standardized-audio-context": "^25.2.4",
|
"standardized-audio-context": "^25.2.4",
|
||||||
|
"ts-proto": "^1.96.0",
|
||||||
"uuidv4": "^6.2.10"
|
"uuidv4": "^6.2.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -18,64 +18,84 @@ class AnalyticsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
identifyUser(uuid: string, email: string | null) {
|
identifyUser(uuid: string, email: string | null) {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.identify(uuid, { uuid, email, wa: true });
|
?.then((posthog) => {
|
||||||
});
|
posthog.identify(uuid, { uuid, email, wa: true });
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithSso() {
|
loggedWithSso() {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-logged-sso");
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-logged-sso");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithToken() {
|
loggedWithToken() {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-logged-token");
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-logged-token");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredRoom(roomId: string, roomGroup: string | null) {
|
enteredRoom(roomId: string, roomGroup: string | null) {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("$pageView", { roomId, roomGroup });
|
?.then((posthog) => {
|
||||||
posthog.capture("enteredRoom");
|
posthog.capture("$pageView", { roomId, roomGroup });
|
||||||
});
|
posthog.capture("enteredRoom");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
openedMenu() {
|
openedMenu() {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-opened-menu");
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-opened-menu");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
launchEmote(emote: string) {
|
launchEmote(emote: string) {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-emote-launch", { emote });
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-emote-launch", { emote });
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredJitsi(roomName: string, roomId: string) {
|
enteredJitsi(roomName: string, roomId: string) {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationName() {
|
validationName() {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-name-validation");
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-name-validation");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationWoka(scene: string) {
|
validationWoka(scene: string) {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-woka-validation", { scene });
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-woka-validation", { scene });
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationVideo() {
|
validationVideo() {
|
||||||
this.posthogPromise?.then((posthog) => {
|
this.posthogPromise
|
||||||
posthog.capture("wa-video-validation");
|
?.then((posthog) => {
|
||||||
});
|
posthog.capture("wa-video-validation");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const analyticsClient = new AnalyticsClient();
|
export const analyticsClient = new AnalyticsClient();
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
import { AdminMessageEventTypes, adminMessagesService } from "../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes, adminMessagesService } from "../Connexion/AdminMessagesService";
|
||||||
import { textMessageContentStore, textMessageVisibleStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||||
import { UPLOADER_URL } from "../Enum/EnvironmentVariable";
|
import { UPLOADER_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { banMessageContentStore, banMessageVisibleStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
|
||||||
class UserMessageManager {
|
class UserMessageManager {
|
||||||
receiveBannedMessageListener!: Function;
|
receiveBannedMessageListener!: Function;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
adminMessagesService.messageStream.subscribe((event) => {
|
adminMessagesService.messageStream.subscribe((event) => {
|
||||||
textMessageVisibleStore.set(false);
|
|
||||||
banMessageVisibleStore.set(false);
|
|
||||||
if (event.type === AdminMessageEventTypes.admin) {
|
if (event.type === AdminMessageEventTypes.admin) {
|
||||||
textMessageContentStore.set(event.text);
|
textMessageStore.addMessage(event.text);
|
||||||
textMessageVisibleStore.set(true);
|
|
||||||
} else if (event.type === AdminMessageEventTypes.audio) {
|
} else if (event.type === AdminMessageEventTypes.audio) {
|
||||||
soundPlayingStore.playSound(UPLOADER_URL + event.text);
|
soundPlayingStore.playSound(UPLOADER_URL + event.text);
|
||||||
} else if (event.type === AdminMessageEventTypes.ban) {
|
} else if (event.type === AdminMessageEventTypes.ban) {
|
||||||
banMessageContentStore.set(event.text);
|
banMessageStore.addMessage(event.text);
|
||||||
banMessageVisibleStore.set(true);
|
|
||||||
} else if (event.type === AdminMessageEventTypes.banned) {
|
} else if (event.type === AdminMessageEventTypes.banned) {
|
||||||
banMessageContentStore.set(event.text);
|
banMessageStore.addMessage(event.text);
|
||||||
banMessageVisibleStore.set(true);
|
|
||||||
this.receiveBannedMessageListener();
|
this.receiveBannedMessageListener();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
13
front/src/Api/Events/ColorEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isColorEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
red: tg.isNumber,
|
||||||
|
green: tg.isNumber,
|
||||||
|
blue: tg.isNumber,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to dynamically set the outline of the player.
|
||||||
|
*/
|
||||||
|
export type ColorEvent = tg.GuardedType<typeof isColorEvent>;
|
@ -34,6 +34,7 @@ import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
|||||||
import type { CameraSetPositionEvent } from "./CameraSetPositionEvent";
|
import type { CameraSetPositionEvent } from "./CameraSetPositionEvent";
|
||||||
import type { CameraFocusOnEvent } from "./CameraFocusOnEvent";
|
import type { CameraFocusOnEvent } from "./CameraFocusOnEvent";
|
||||||
import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
|
import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
|
||||||
|
import { isColorEvent } from "./ColorEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T;
|
data: T;
|
||||||
@ -162,6 +163,14 @@ export const iframeQueryMapTypeGuards = {
|
|||||||
query: isCreateEmbeddedWebsiteEvent,
|
query: isCreateEmbeddedWebsiteEvent,
|
||||||
answer: tg.isUndefined,
|
answer: tg.isUndefined,
|
||||||
},
|
},
|
||||||
|
setPlayerOutline: {
|
||||||
|
query: isColorEvent,
|
||||||
|
answer: tg.isUndefined,
|
||||||
|
},
|
||||||
|
removePlayerOutline: {
|
||||||
|
query: tg.isUndefined,
|
||||||
|
answer: tg.isUndefined,
|
||||||
|
},
|
||||||
getPlayerPosition: {
|
getPlayerPosition: {
|
||||||
query: tg.isUndefined,
|
query: tg.isUndefined,
|
||||||
answer: isPlayerPosition,
|
answer: isPlayerPosition,
|
||||||
|
@ -26,7 +26,7 @@ export class ActionMessage {
|
|||||||
this.message = actionMessageOptions.message;
|
this.message = actionMessageOptions.message;
|
||||||
this.type = actionMessageOptions.type ?? "message";
|
this.type = actionMessageOptions.type ?? "message";
|
||||||
this.callback = actionMessageOptions.callback;
|
this.callback = actionMessageOptions.callback;
|
||||||
this.create();
|
this.create().catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async create() {
|
private async create() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import type { WasCameraUpdatedEvent, WasCameraUpdatedEventCallback } from "../Events/WasCameraUpdatedEvent";
|
import type { WasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
||||||
|
|
||||||
@ -38,12 +38,12 @@ export class WorkAdventureCameraCommands extends IframeApiContribution<WorkAdven
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCameraUpdate(callback: WasCameraUpdatedEventCallback): void {
|
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
||||||
moveStream.subscribe(callback);
|
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: "onCameraUpdate",
|
type: "onCameraUpdate",
|
||||||
data: null,
|
data: null,
|
||||||
});
|
});
|
||||||
|
return moveStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,24 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
|||||||
}
|
}
|
||||||
return userRoomToken;
|
return userRoomToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setOutlineColor(red: number, green: number, blue: number): Promise<void> {
|
||||||
|
return queryWorkadventure({
|
||||||
|
type: "setPlayerOutline",
|
||||||
|
data: {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
blue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeOutlineColor(): Promise<void> {
|
||||||
|
return queryWorkadventure({
|
||||||
|
type: "removePlayerOutline",
|
||||||
|
data: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Position = {
|
export type Position = {
|
||||||
|
@ -95,7 +95,7 @@ export function createState(target: "global" | "player"): WorkadventureStateComm
|
|||||||
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
|
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
|
||||||
// Note: when using "set", there is no way to wait, so we ignore the return of the promise.
|
// Note: when using "set", there is no way to wait, so we ignore the return of the promise.
|
||||||
// User must use WA.state.saveVariable to have error message.
|
// User must use WA.state.saveVariable to have error message.
|
||||||
target.saveVariable(p.toString(), value);
|
target.saveVariable(p.toString(), value).catch((e) => console.error(e));
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
|
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||||
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
||||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||||
|
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
||||||
|
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
||||||
|
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
||||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
@ -30,10 +33,10 @@
|
|||||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||||
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
||||||
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
||||||
import AdminMessage from "./TypeMessage/BanMessage.svelte";
|
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
||||||
import TextMessage from "./TypeMessage/TextMessage.svelte";
|
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
||||||
import { banMessageVisibleStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
import { textMessageVisibleStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
import { warningContainerStore } from "../Stores/MenuStore";
|
import { warningContainerStore } from "../Stores/MenuStore";
|
||||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||||
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||||
@ -42,6 +45,9 @@
|
|||||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||||
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||||
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||||
|
import { followStateStore } from "../Stores/FollowStore";
|
||||||
|
import { peerStore } from "../Stores/PeerStore";
|
||||||
|
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
</script>
|
</script>
|
||||||
@ -72,14 +78,13 @@
|
|||||||
<EnableCameraScene {game} />
|
<EnableCameraScene {game} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $banMessageVisibleStore}
|
{#if $banMessageStore.length > 0}
|
||||||
<div>
|
<div>
|
||||||
<AdminMessage />
|
<BanMessageContainer />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{:else if $textMessageStore.length > 0}
|
||||||
{#if $textMessageVisibleStore}
|
|
||||||
<div>
|
<div>
|
||||||
<TextMessage />
|
<TextMessageContainer />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $soundPlayingStore}
|
{#if $soundPlayingStore}
|
||||||
@ -102,6 +107,11 @@
|
|||||||
<ReportMenu />
|
<ReportMenu />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||||
|
<div>
|
||||||
|
<FollowMenu />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if $menuIconVisiblilityStore}
|
{#if $menuIconVisiblilityStore}
|
||||||
<div>
|
<div>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
@ -129,6 +139,16 @@
|
|||||||
<HelpCameraSettingsPopup />
|
<HelpCameraSettingsPopup />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $showLimitRoomModalStore}
|
||||||
|
<div>
|
||||||
|
<LimitRoomModal />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $showShareLinkMapModalStore}
|
||||||
|
<div>
|
||||||
|
<ShareLinkMapModal />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if $requestVisitCardsStore}
|
{#if $requestVisitCardsStore}
|
||||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -19,12 +19,13 @@
|
|||||||
audioManagerVolumeStore.setVolume(volume);
|
audioManagerVolumeStore.setVolume(volume);
|
||||||
audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted());
|
audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted());
|
||||||
|
|
||||||
unsubscriberFileStore = audioManagerFileStore.subscribe(() => {
|
unsubscriberFileStore = audioManagerFileStore.subscribe((src) => {
|
||||||
HTMLAudioPlayer.pause();
|
HTMLAudioPlayer.pause();
|
||||||
|
HTMLAudioPlayer.src = src;
|
||||||
HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop;
|
HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop;
|
||||||
HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume;
|
HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume;
|
||||||
HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted;
|
HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted;
|
||||||
HTMLAudioPlayer.play();
|
void HTMLAudioPlayer.play();
|
||||||
});
|
});
|
||||||
unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => {
|
unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => {
|
||||||
const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking;
|
const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking;
|
||||||
@ -148,9 +149,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<section class="audio-manager-file">
|
<section class="audio-manager-file">
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
<audio class="audio-manager-audioplayer" bind:this={HTMLAudioPlayer}>
|
<audio class="audio-manager-audioplayer" bind:this={HTMLAudioPlayer} />
|
||||||
<source src={$audioManagerFileStore} />
|
|
||||||
</audio>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
.messagePart {
|
.messagePart {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
span.date {
|
span.date {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
|
197
front/src/Components/FollowMenu/FollowMenu.svelte
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<!--
|
||||||
|
vim: ft=typescript
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import followImg from "../images/follow.svg";
|
||||||
|
|
||||||
|
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
|
|
||||||
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
|
function name(userId: number): string | undefined {
|
||||||
|
return gameScene.MapPlayersByKey.get(userId)?.PlayerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendFollowRequest() {
|
||||||
|
gameScene.CurrentPlayer.sendFollowRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptFollowRequest() {
|
||||||
|
gameScene.CurrentPlayer.startFollowing();
|
||||||
|
}
|
||||||
|
|
||||||
|
function abortEnding() {
|
||||||
|
followStateStore.set("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
gameScene.connection?.emitFollowAbort();
|
||||||
|
followUsersStore.stopFollowing();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
|
{#if $followStateStore === "requesting" && $followRoleStore === "follower"}
|
||||||
|
<div class="interact-menu nes-container is-rounded">
|
||||||
|
<section class="interact-menu-title">
|
||||||
|
<h2>Do you want to follow {name($followUsersStore[0])}?</h2>
|
||||||
|
</section>
|
||||||
|
<section class="interact-menu-action">
|
||||||
|
<button type="button" class="nes-btn is-success" on:click|preventDefault={acceptFollowRequest}>Yes</button>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={reset}>No</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "ending"}
|
||||||
|
<div class="interact-menu nes-container is-rounded">
|
||||||
|
<section class="interact-menu-title">
|
||||||
|
<h2>Interaction</h2>
|
||||||
|
</section>
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<section class="interact-menu-question">
|
||||||
|
<p>Do you want to stop following {name($followUsersStore[0])}?</p>
|
||||||
|
</section>
|
||||||
|
{:else if $followRoleStore === "leader"}
|
||||||
|
<section class="interact-menu-question">
|
||||||
|
<p>Do you want to stop leading the way?</p>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
<section class="interact-menu-action">
|
||||||
|
<button type="button" class="nes-btn is-success" on:click|preventDefault={reset}>Yes</button>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={abortEnding}>No</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||||
|
<div class="interact-status nes-container is-rounded">
|
||||||
|
<section class="interact-status">
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<p>Following {name($followUsersStore[0])}</p>
|
||||||
|
{:else if $followUsersStore.length === 0}
|
||||||
|
<p>Waiting for followers' confirmation</p>
|
||||||
|
{:else if $followUsersStore.length === 1}
|
||||||
|
<p>{name($followUsersStore[0])} is following you</p>
|
||||||
|
{:else if $followUsersStore.length === 2}
|
||||||
|
<p>{name($followUsersStore[0])} and {name($followUsersStore[1])} are following you</p>
|
||||||
|
{:else}
|
||||||
|
<p>
|
||||||
|
{$followUsersStore.slice(0, -1).map(name).join(", ")} and {name(
|
||||||
|
$followUsersStore[$followUsersStore.length - 1]
|
||||||
|
)} are following you
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "off"}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-primary follow-menu-button"
|
||||||
|
on:click|preventDefault={sendFollowRequest}
|
||||||
|
title="Ask others to follow"><img class="background-img" src={followImg} alt="" /></button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-error follow-menu-button"
|
||||||
|
on:click|preventDefault={reset}
|
||||||
|
title="Stop following"><img class="background-img" src={followImg} alt="" /></button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-error follow-menu-button"
|
||||||
|
on:click|preventDefault={reset}
|
||||||
|
title="Stop leading the way"><img class="background-img" src={followImg} alt="" /></button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.nes-container {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interact-status {
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: 2.7em;
|
||||||
|
width: 40vw;
|
||||||
|
top: 87vh;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interact-menu {
|
||||||
|
pointer-events: auto;
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
width: 60vw;
|
||||||
|
top: 60vh;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
section.interact-menu-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.interact-menu-question {
|
||||||
|
margin: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.05em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.interact-menu-action {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 10%;
|
||||||
|
grid-template-columns: 45% 45%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow-menu-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
div.interact-status {
|
||||||
|
width: 100vw;
|
||||||
|
top: 78vh;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interact-menu {
|
||||||
|
height: 21vh;
|
||||||
|
width: 100vw;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,11 +6,14 @@
|
|||||||
|
|
||||||
let expandedMapCopyright = false;
|
let expandedMapCopyright = false;
|
||||||
let expandedTilesetCopyright = false;
|
let expandedTilesetCopyright = false;
|
||||||
|
let expandedAudioCopyright = false;
|
||||||
|
|
||||||
let mapName: string = "";
|
let mapName: string = "";
|
||||||
|
let mapLink: string = "";
|
||||||
let mapDescription: string = "";
|
let mapDescription: string = "";
|
||||||
let mapCopyright: string = "The map creator did not declare a copyright for the map.";
|
let mapCopyright: string = "The map creator did not declare a copyright for the map.";
|
||||||
let tilesetCopyright: string[] = [];
|
let tilesetCopyright: string[] = [];
|
||||||
|
let audioCopyright: string[] = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (gameScene.mapFile.properties !== undefined) {
|
if (gameScene.mapFile.properties !== undefined) {
|
||||||
@ -18,6 +21,10 @@
|
|||||||
if (propertyName !== undefined && typeof propertyName.value === "string") {
|
if (propertyName !== undefined && typeof propertyName.value === "string") {
|
||||||
mapName = propertyName.value;
|
mapName = propertyName.value;
|
||||||
}
|
}
|
||||||
|
const propertyLink = gameScene.mapFile.properties.find((property) => property.name === "mapLink");
|
||||||
|
if (propertyLink !== undefined && typeof propertyLink.value === "string") {
|
||||||
|
mapLink = propertyLink.value;
|
||||||
|
}
|
||||||
const propertyDescription = gameScene.mapFile.properties.find(
|
const propertyDescription = gameScene.mapFile.properties.find(
|
||||||
(property) => property.name === "mapDescription"
|
(property) => property.name === "mapDescription"
|
||||||
);
|
);
|
||||||
@ -36,7 +43,18 @@
|
|||||||
(property) => property.name === "tilesetCopyright"
|
(property) => property.name === "tilesetCopyright"
|
||||||
);
|
);
|
||||||
if (propertyTilesetCopyright !== undefined && typeof propertyTilesetCopyright.value === "string") {
|
if (propertyTilesetCopyright !== undefined && typeof propertyTilesetCopyright.value === "string") {
|
||||||
tilesetCopyright = [...tilesetCopyright, propertyTilesetCopyright.value]; //Assignment needed to trigger Svelte's reactivity
|
// Assignment needed to trigger Svelte's reactivity
|
||||||
|
tilesetCopyright = [...tilesetCopyright, propertyTilesetCopyright.value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const layer of gameScene.mapFile.layers) {
|
||||||
|
if (layer.type && layer.type === "tilelayer" && layer.properties) {
|
||||||
|
const propertyAudioCopyright = layer.properties.find((property) => property.name === "audioCopyright");
|
||||||
|
if (propertyAudioCopyright !== undefined && typeof propertyAudioCopyright.value === "string") {
|
||||||
|
// Assignment needed to trigger Svelte's reactivity
|
||||||
|
audioCopyright = [...audioCopyright, propertyAudioCopyright.value];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +66,9 @@
|
|||||||
<section class="container-overflow">
|
<section class="container-overflow">
|
||||||
<h3>{mapName}</h3>
|
<h3>{mapName}</h3>
|
||||||
<p class="string-HTML">{mapDescription}</p>
|
<p class="string-HTML">{mapDescription}</p>
|
||||||
|
{#if mapLink}
|
||||||
|
<p class="string-HTML">> <a href={mapLink} target="_blank">link to this map</a> <</p>
|
||||||
|
{/if}
|
||||||
<h3 class="nes-pointer hoverable" on:click={() => (expandedMapCopyright = !expandedMapCopyright)}>
|
<h3 class="nes-pointer hoverable" on:click={() => (expandedMapCopyright = !expandedMapCopyright)}>
|
||||||
Copyrights of the map
|
Copyrights of the map
|
||||||
</h3>
|
</h3>
|
||||||
@ -60,8 +81,21 @@
|
|||||||
<p class="string-HTML">{copyright}</p>
|
<p class="string-HTML">{copyright}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p>
|
||||||
The map creator did not declare a copyright for the tilesets. Warning, This doesn't mean that those
|
The map creator did not declare a copyright for the tilesets. This doesn't mean that those tilesets
|
||||||
tilesets have no license.
|
have no license.
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
<h3 class="nes-pointer hoverable" on:click={() => (expandedAudioCopyright = !expandedAudioCopyright)}>
|
||||||
|
Copyrights of audio files
|
||||||
|
</h3>
|
||||||
|
<section hidden={!expandedAudioCopyright}>
|
||||||
|
{#each audioCopyright as copyright}
|
||||||
|
<p class="string-HTML">{copyright}</p>
|
||||||
|
{:else}
|
||||||
|
<p>
|
||||||
|
The map creator did not declare a copyright for audio files. This doesn't mean that those tilesets
|
||||||
|
have no license.
|
||||||
</p>
|
</p>
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
||||||
if (!selectedFile) {
|
if (!selectedFile) {
|
||||||
errorFile = true;
|
errorFile = true;
|
||||||
throw "no file selected";
|
throw new Error("no file selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
uploadAudioActive = true;
|
uploadAudioActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function send() {
|
async function send(): Promise<void> {
|
||||||
if (inputSendTextActive) {
|
if (inputSendTextActive) {
|
||||||
handleSendText.sendTextMessage(broadcastToWorld);
|
return handleSendText.sendTextMessage(broadcastToWorld);
|
||||||
}
|
}
|
||||||
if (uploadAudioActive) {
|
if (uploadAudioActive) {
|
||||||
handleSendAudio.sendAudioMessage(broadcastToWorld);
|
return handleSendAudio.sendAudioMessage(broadcastToWorld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -21,12 +21,12 @@
|
|||||||
<div class="guest-main">
|
<div class="guest-main">
|
||||||
<section class="container-overflow">
|
<section class="container-overflow">
|
||||||
<section class="share-url not-mobile">
|
<section class="share-url not-mobile">
|
||||||
<h3>Share the link of the room !</h3>
|
<h3>Share the link of the room!</h3>
|
||||||
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
||||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
||||||
</section>
|
</section>
|
||||||
<section class="is-mobile">
|
<section class="is-mobile">
|
||||||
<h3>Share the link of the room !</h3>
|
<h3>Share the link of the room!</h3>
|
||||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
||||||
</section>
|
</section>
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else throw "There is no menu called " + menu;
|
} else throw new Error("There is no menu called " + menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import logoTalk from "../images/logo-message-pixel.png";
|
import logoTalk from "../images/logo-message-pixel.png";
|
||||||
import logoWA from "../images/logo-WA-pixel.png";
|
import logoWA from "../images/logo-WA-pixel.png";
|
||||||
|
import logoInvite from "../images/logo-invite-pixel.png";
|
||||||
|
import logoRegister from "../images/logo-register-pixel.png";
|
||||||
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
||||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||||
|
import { limitMapStore } from "../../Stores/GameStore";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
||||||
|
|
||||||
function showMenu() {
|
function showMenu() {
|
||||||
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
||||||
@ -11,13 +16,25 @@
|
|||||||
function showChat() {
|
function showChat() {
|
||||||
chatVisibilityStore.set(true);
|
chatVisibilityStore.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function register() {
|
||||||
|
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
||||||
|
}
|
||||||
|
function showInvite() {
|
||||||
|
showShareLinkMapModalStore.set(true);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window />
|
<svelte:window />
|
||||||
|
|
||||||
<main class="menuIcon">
|
<main class="menuIcon">
|
||||||
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu} />
|
{#if $limitMapStore}
|
||||||
<img src={logoTalk} alt="open menu" class="nes-pointer" on:click|preventDefault={showChat} />
|
<img src={logoInvite} alt="open menu" class="nes-pointer" on:click|preventDefault={showInvite} />
|
||||||
|
<img src={logoRegister} alt="open menu" class="nes-pointer" on:click|preventDefault={register} />
|
||||||
|
{:else}
|
||||||
|
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu} />
|
||||||
|
<img src={logoTalk} alt="open menu" class="nes-pointer" on:click|preventDefault={showChat} />
|
||||||
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -41,10 +41,10 @@
|
|||||||
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
||||||
}
|
}
|
||||||
|
|
||||||
function logOut() {
|
async function logOut() {
|
||||||
disableMenuStores();
|
disableMenuStores();
|
||||||
loginSceneVisibleStore.set(true);
|
loginSceneVisibleStore.set(true);
|
||||||
connectionManager.logout();
|
return connectionManager.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProfileUrl() {
|
function getProfileUrl() {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||||
let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger();
|
let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger();
|
||||||
|
let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests();
|
||||||
let valueGame: number = localUserStore.getGameQualityValue();
|
let valueGame: number = localUserStore.getGameQualityValue();
|
||||||
let valueVideo: number = localUserStore.getVideoQualityValue();
|
let valueVideo: number = localUserStore.getVideoQualityValue();
|
||||||
let previewValueGame = valueGame;
|
let previewValueGame = valueGame;
|
||||||
@ -32,9 +33,9 @@
|
|||||||
const body = HtmlUtils.querySelectorOrFail("body");
|
const body = HtmlUtils.querySelectorOrFail("body");
|
||||||
if (body) {
|
if (body) {
|
||||||
if (document.fullscreenElement !== null && !fullscreen) {
|
if (document.fullscreenElement !== null && !fullscreen) {
|
||||||
document.exitFullscreen();
|
document.exitFullscreen().catch((e) => console.error(e));
|
||||||
} else {
|
} else {
|
||||||
body.requestFullscreen();
|
body.requestFullscreen().catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
localUserStore.setFullscreen(fullscreen);
|
localUserStore.setFullscreen(fullscreen);
|
||||||
}
|
}
|
||||||
@ -44,14 +45,16 @@
|
|||||||
if (Notification.permission === "granted") {
|
if (Notification.permission === "granted") {
|
||||||
localUserStore.setNotification(notification ? "granted" : "denied");
|
localUserStore.setNotification(notification ? "granted" : "denied");
|
||||||
} else {
|
} else {
|
||||||
Notification.requestPermission().then((response) => {
|
Notification.requestPermission()
|
||||||
if (response === "granted") {
|
.then((response) => {
|
||||||
localUserStore.setNotification(notification ? "granted" : "denied");
|
if (response === "granted") {
|
||||||
} else {
|
localUserStore.setNotification(notification ? "granted" : "denied");
|
||||||
localUserStore.setNotification("denied");
|
} else {
|
||||||
notification = false;
|
localUserStore.setNotification("denied");
|
||||||
}
|
notification = false;
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +62,10 @@
|
|||||||
localUserStore.setForceCowebsiteTrigger(forceCowebsiteTrigger);
|
localUserStore.setForceCowebsiteTrigger(forceCowebsiteTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeIgnoreFollowRequests() {
|
||||||
|
localUserStore.setIgnoreFollowRequests(ignoreFollowRequests);
|
||||||
|
}
|
||||||
|
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
}
|
}
|
||||||
@ -123,6 +130,15 @@
|
|||||||
/>
|
/>
|
||||||
<span>Always ask before opening websites and Jitsi Meet rooms</span>
|
<span>Always ask before opening websites and Jitsi Meet rooms</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="nes-checkbox is-dark"
|
||||||
|
bind:checked={ignoreFollowRequests}
|
||||||
|
on:change={changeIgnoreFollowRequests}
|
||||||
|
/>
|
||||||
|
<span>Ignore requests to follow other users</span>
|
||||||
|
</label>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
47
front/src/Components/Modal/LimitRoomModal.svelte
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
function register() {
|
||||||
|
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="limit-map nes-container" transition:fly={{ y: -900, duration: 500 }}>
|
||||||
|
<section>
|
||||||
|
<h2>Limit of your room</h2>
|
||||||
|
<p>Register your account!</p>
|
||||||
|
<p>
|
||||||
|
This map is limited in the time and to continue to use WorkAdventure, you must register your account in our
|
||||||
|
back office.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button class="nes-btn is-primary" on:click|preventDefault={register}>Register</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.limit-map {
|
||||||
|
pointer-events: auto;
|
||||||
|
background: #eceeee;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 10vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
max-width: 80vw;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
p {
|
||||||
|
margin: 15px;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
90
front/src/Components/Modal/ShareLinkMapModal.svelte
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
||||||
|
|
||||||
|
interface ExtNavigator extends Navigator {
|
||||||
|
canShare?(data?: ShareData): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const myNavigator: ExtNavigator = window.navigator;
|
||||||
|
const haveNavigatorSharingFeature: boolean =
|
||||||
|
myNavigator && myNavigator.canShare != null && myNavigator.share != null;
|
||||||
|
|
||||||
|
let copied: boolean = false;
|
||||||
|
|
||||||
|
function copyLink() {
|
||||||
|
try {
|
||||||
|
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
copied = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
copied = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareLink() {
|
||||||
|
const shareData = { url: location.toString() };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await myNavigator.share(shareData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error: " + err);
|
||||||
|
copyLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
showShareLinkMapModalStore.set(false);
|
||||||
|
copied = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="share-link-map nes-container" transition:fly={{ y: -900, duration: 500 }}>
|
||||||
|
<section>
|
||||||
|
<h2>Invite your friends or colleagues</h2>
|
||||||
|
<p>Share the link of the room!</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
{#if haveNavigatorSharingFeature}
|
||||||
|
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
||||||
|
{:else}
|
||||||
|
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
||||||
|
{/if}
|
||||||
|
{#if copied}
|
||||||
|
<p>Copied!</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button class="nes-btn" on:click|preventDefault={close}>Close</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.share-link-map {
|
||||||
|
pointer-events: auto;
|
||||||
|
background: #eceeee;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 10vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
max-width: 80vw;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
p {
|
||||||
|
margin: 15px;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,12 +6,11 @@
|
|||||||
import type { Unsubscriber } from "svelte/store";
|
import type { Unsubscriber } from "svelte/store";
|
||||||
import { playersStore } from "../../Stores/PlayersStore";
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import { GameConnexionTypes } from "../../Url/UrlManager";
|
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
let blockActive = true;
|
let blockActive = true;
|
||||||
let reportActive = !blockActive;
|
let reportActive = !blockActive;
|
||||||
let anonymous: boolean = false;
|
let disableReport: boolean = false;
|
||||||
let userUUID: string | undefined = playersStore.getPlayerById(get(showReportScreenStore).userId)?.userUuid;
|
let userUUID: string | undefined = playersStore.getPlayerById(get(showReportScreenStore).userId)?.userUuid;
|
||||||
let userName = "No name";
|
let userName = "No name";
|
||||||
let unsubscriber: Unsubscriber;
|
let unsubscriber: Unsubscriber;
|
||||||
@ -26,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
anonymous = connectionManager.getConnexionType === GameConnexionTypes.anonymous;
|
disableReport = !connectionManager.currentRoom?.canReport ?? true;
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -65,7 +64,7 @@
|
|||||||
<button type="button" class="nes-btn" on:click|preventDefault={close}>X</button>
|
<button type="button" class="nes-btn" on:click|preventDefault={close}>X</button>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section class="report-menu-action {anonymous ? 'hidden' : ''}">
|
<section class="report-menu-action {disableReport ? 'hidden' : ''}">
|
||||||
<section class="justify-center">
|
<section class="justify-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from "svelte/transition";
|
import { fly, fade } from "svelte/transition";
|
||||||
import { banMessageVisibleStore, banMessageContentStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||||
|
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
|
||||||
|
export let message: Message;
|
||||||
|
|
||||||
const text = $banMessageContentStore;
|
|
||||||
const NAME_BUTTON = "Ok";
|
const NAME_BUTTON = "Ok";
|
||||||
let nbSeconds = 10;
|
let nbSeconds = 10;
|
||||||
let nameButton = "";
|
let nameButton = "";
|
||||||
@ -25,17 +27,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeBanMessage() {
|
function closeBanMessage() {
|
||||||
banMessageVisibleStore.set(false);
|
banMessageStore.clearMessageById(message.id);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="main-ban-message nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
|
<div
|
||||||
|
class="main-ban-message nes-container is-rounded"
|
||||||
|
in:fly={{ y: -1000, duration: 500, delay: 250 }}
|
||||||
|
out:fade={{ duration: 200 }}
|
||||||
|
>
|
||||||
<h2 class="title-ban-message">
|
<h2 class="title-ban-message">
|
||||||
<img src="resources/logos/report.svg" alt="***" /> Important message
|
<img src="resources/logos/report.svg" alt="***" /> Important message
|
||||||
<img src="resources/logos/report.svg" alt="***" />
|
<img src="resources/logos/report.svg" alt="***" />
|
||||||
</h2>
|
</h2>
|
||||||
<div class="content-ban-message">
|
<div class="content-ban-message">
|
||||||
<p>{text}</p>
|
<p>{message.text}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-ban-message">
|
<div class="footer-ban-message">
|
||||||
<button
|
<button
|
||||||
|
13
front/src/Components/TypeMessage/BanMessageContainer.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { flip } from "svelte/animate";
|
||||||
|
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import BanMessage from "./BanMessage.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main-ban-message-container">
|
||||||
|
{#each $banMessageStore.slice(0, 1) as message (message.id)}
|
||||||
|
<div animate:flip={{ duration: 250 }}>
|
||||||
|
<BanMessage {message} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
@ -1,14 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from "svelte/transition";
|
import { fly, fade } from "svelte/transition";
|
||||||
import { textMessageContentStore, textMessageVisibleStore } from "../../Stores/TypeMessageStore/TextMessageStore";
|
|
||||||
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
|
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
|
||||||
|
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||||
|
import { textMessageStore } from "../../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
|
||||||
const content = JSON.parse($textMessageContentStore);
|
export let message: Message;
|
||||||
|
|
||||||
|
const content = JSON.parse(message.text);
|
||||||
const converter = new QuillDeltaToHtmlConverter(content.ops, { inlineStyles: true });
|
const converter = new QuillDeltaToHtmlConverter(content.ops, { inlineStyles: true });
|
||||||
const NAME_BUTTON = "Ok";
|
const NAME_BUTTON = "Ok";
|
||||||
|
|
||||||
function closeTextMessage() {
|
function closeTextMessage() {
|
||||||
textMessageVisibleStore.set(false);
|
textMessageStore.clearMessageById(message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
@ -20,7 +23,11 @@
|
|||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown} />
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
<div class="main-text-message nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
|
<div
|
||||||
|
class="main-text-message nes-container is-rounded"
|
||||||
|
in:fly={{ x: -1000, duration: 500, delay: 250 }}
|
||||||
|
out:fade={{ duration: 250 }}
|
||||||
|
>
|
||||||
<div class="content-text-message">
|
<div class="content-text-message">
|
||||||
{@html converter.convert()}
|
{@html converter.convert()}
|
||||||
</div>
|
</div>
|
||||||
@ -40,6 +47,8 @@
|
|||||||
width: 80vw;
|
width: 80vw;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
21
front/src/Components/TypeMessage/TextMessageContainer.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { flip } from "svelte/animate";
|
||||||
|
import TextMessage from "./TextMessage.svelte";
|
||||||
|
import { textMessageStore } from "../../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
|
||||||
|
const MAX_MESSAGES = 3;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main-text-message-container">
|
||||||
|
{#each $textMessageStore.slice(0, MAX_MESSAGES) as message (message.id)}
|
||||||
|
<div animate:flip={{ duration: 250 }}>
|
||||||
|
<TextMessage {message} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.main-text-message-container {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterUpdate(() => {
|
afterUpdate(() => {
|
||||||
audio.play();
|
audio.play().catch((e) => console.error(e));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,20 +1,26 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
import { userIsAdminStore, limitMapStore } from "../../Stores/GameStore";
|
||||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
const upgradeLink = ADMIN_URL + "/pricing";
|
const upgradeLink = ADMIN_URL + "/pricing";
|
||||||
|
const registerLink = ADMIN_URL + "/second-step-register";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
|
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
|
||||||
<h2>Warning!</h2>
|
|
||||||
{#if $userIsAdminStore}
|
{#if $userIsAdminStore}
|
||||||
|
<h2>Warning!</h2>
|
||||||
<p>
|
<p>
|
||||||
This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank"
|
This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank"
|
||||||
>here</a
|
>here</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
{:else if $limitMapStore}
|
||||||
|
<p>
|
||||||
|
This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>!
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
|
<h2>Warning!</h2>
|
||||||
<p>This world is close to its limit!</p>
|
<p>This world is close to its limit!</p>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
1
front/src/Components/images/follow.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><rect fill="none" height="24" width="24"/><path d="M9.5,5.5c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S8.4,5.5,9.5,5.5z M5.75,8.9L3,23h2.1l1.75-8L9,17v6h2v-7.55L8.95,13.4 l0.6-3C10.85,12,12.8,13,15,13v-2c-1.85,0-3.45-1-4.35-2.45L9.7,6.95C9.35,6.35,8.7,6,8,6C7.75,6,7.5,6.05,7.25,6.15L2,8.3V13h2 V9.65L5.75,8.9 M13,2v7h3.75v14h1.5V9H22V2H13z M18.01,8V6.25H14.5v-1.5h3.51V3l2.49,2.5L18.01,8z"/></svg>
|
After Width: | Height: | Size: 510 B |
BIN
front/src/Components/images/logo-invite-pixel.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
front/src/Components/images/logo-register-pixel.png
Normal file
After Width: | Height: | Size: 977 B |
@ -1,5 +1,5 @@
|
|||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import type { BanUserMessage, SendUserMessage } from "../Messages/generated/messages_pb";
|
import type { BanUserMessage, SendUserMessage } from "../Messages/ts-proto-generated/messages";
|
||||||
|
|
||||||
export enum AdminMessageEventTypes {
|
export enum AdminMessageEventTypes {
|
||||||
admin = "message",
|
admin = "message",
|
||||||
@ -26,8 +26,8 @@ class AdminMessagesService {
|
|||||||
|
|
||||||
onSendusermessage(message: SendUserMessage | BanUserMessage) {
|
onSendusermessage(message: SendUserMessage | BanUserMessage) {
|
||||||
this._messageStream.next({
|
this._messageStream.next({
|
||||||
type: message.getType() as unknown as AdminMessageEventTypes,
|
type: message.type as unknown as AdminMessageEventTypes,
|
||||||
text: message.getMessage(),
|
text: message.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { PUSHER_URL, START_ROOM_URL } from "../Enum/EnvironmentVariable";
|
import { PUSHER_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { RoomConnection } from "./RoomConnection";
|
import { RoomConnection } from "./RoomConnection";
|
||||||
import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels";
|
import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels";
|
||||||
import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
|
import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
|
||||||
@ -8,9 +8,14 @@ import { CharacterTexture, LocalUser } from "./LocalUser";
|
|||||||
import { Room } from "./Room";
|
import { Room } from "./Room";
|
||||||
import { _ServiceWorker } from "../Network/ServiceWorker";
|
import { _ServiceWorker } from "../Network/ServiceWorker";
|
||||||
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
|
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
|
||||||
import { userIsConnected } from "../Stores/MenuStore";
|
import { userIsConnected, warningContainerStore } from "../Stores/MenuStore";
|
||||||
import { analyticsClient } from "../Administration/AnalyticsClient";
|
import { analyticsClient } from "../Administration/AnalyticsClient";
|
||||||
import { axiosWithRetry } from "./AxiosUtils";
|
import { axiosWithRetry } from "./AxiosUtils";
|
||||||
|
import axios from "axios";
|
||||||
|
import { isRegisterData } from "../Messages/JsonMessages/RegisterData";
|
||||||
|
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||||
|
import { limitMapStore } from "../Stores/GameStore";
|
||||||
|
import { showLimitRoomModalStore } from "../Stores/ModalStore";
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!: LocalUser;
|
private localUser!: LocalUser;
|
||||||
@ -101,10 +106,10 @@ class ConnectionManager {
|
|||||||
const code = urlParams.get("code");
|
const code = urlParams.get("code");
|
||||||
const state = urlParams.get("state");
|
const state = urlParams.get("state");
|
||||||
if (!state || !localUserStore.verifyState(state)) {
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
throw "Could not validate state!";
|
throw new Error("Could not validate state!");
|
||||||
}
|
}
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw "No Auth code provided";
|
throw new Error("No Auth code provided");
|
||||||
}
|
}
|
||||||
localUserStore.setCode(code);
|
localUserStore.setCode(code);
|
||||||
}
|
}
|
||||||
@ -125,6 +130,10 @@ class ConnectionManager {
|
|||||||
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
|
||||||
(res) => res.data
|
(res) => res.data
|
||||||
);
|
);
|
||||||
|
if (!isRegisterData(data)) {
|
||||||
|
console.error("Invalid data received from /register route. Data: ", data);
|
||||||
|
throw new Error("Invalid data received from /register route.");
|
||||||
|
}
|
||||||
this.localUser = new LocalUser(data.userUuid, data.textures, data.email);
|
this.localUser = new LocalUser(data.userUuid, data.textures, data.email);
|
||||||
this.authToken = data.authToken;
|
this.authToken = data.authToken;
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
@ -145,11 +154,7 @@ class ConnectionManager {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||||
} else if (
|
} else if (connexionType === GameConnexionTypes.room || connexionType === GameConnexionTypes.empty) {
|
||||||
connexionType === GameConnexionTypes.organization ||
|
|
||||||
connexionType === GameConnexionTypes.anonymous ||
|
|
||||||
connexionType === GameConnexionTypes.empty
|
|
||||||
) {
|
|
||||||
this.authToken = localUserStore.getAuthToken();
|
this.authToken = localUserStore.getAuthToken();
|
||||||
|
|
||||||
let roomPath: string;
|
let roomPath: string;
|
||||||
@ -163,6 +168,9 @@ class ConnectionManager {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
if (err instanceof Error) {
|
||||||
|
console.error(err.stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const query = urlParams.toString();
|
const query = urlParams.toString();
|
||||||
@ -181,7 +189,7 @@ class ConnectionManager {
|
|||||||
|
|
||||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||||
//use href to keep # value
|
//use href to keep # value
|
||||||
localUserStore.setLastRoomUrl(this._currentRoom.href);
|
await localUserStore.setLastRoomUrl(this._currentRoom.href);
|
||||||
|
|
||||||
//todo: add here some kind of warning if authToken has expired.
|
//todo: add here some kind of warning if authToken has expired.
|
||||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||||
@ -192,11 +200,13 @@ class ConnectionManager {
|
|||||||
analyticsClient.loggedWithSso();
|
analyticsClient.loggedWithSso();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
//if user must to be connect in current room or pusher error is not openid provier access error
|
// if the user must be connected in the current room or if the pusher error is not openid provider access error
|
||||||
//try to connected with function loadOpenIDScreen
|
// try to connect with function loadOpenIDScreen
|
||||||
if (
|
if (
|
||||||
this._currentRoom.authenticationMandatory ||
|
this._currentRoom.authenticationMandatory ||
|
||||||
(err.response?.data && err.response.data !== "User cannot to be connected on openid provier")
|
(axios.isAxiosError(err) &&
|
||||||
|
err.response?.data &&
|
||||||
|
err.response.data !== "User cannot to be connected on openid provider")
|
||||||
) {
|
) {
|
||||||
this.loadOpenIDScreen();
|
this.loadOpenIDScreen();
|
||||||
return Promise.reject(new Error("You will be redirect on login page"));
|
return Promise.reject(new Error("You will be redirect on login page"));
|
||||||
@ -228,6 +238,17 @@ class ConnectionManager {
|
|||||||
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
|
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if limit room active test headband
|
||||||
|
if (this._currentRoom.expireOn !== undefined) {
|
||||||
|
warningContainerStore.activateWarningContainer();
|
||||||
|
limitMapStore.set(true);
|
||||||
|
|
||||||
|
//check time of map
|
||||||
|
if (new Date() > this._currentRoom.expireOn) {
|
||||||
|
showLimitRoomModalStore.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.serviceWorker = new _ServiceWorker();
|
this.serviceWorker = new _ServiceWorker();
|
||||||
return Promise.resolve(this._currentRoom);
|
return Promise.resolve(this._currentRoom);
|
||||||
}
|
}
|
||||||
@ -271,7 +292,7 @@ class ConnectionManager {
|
|||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onConnectingError((event: CloseEvent) => {
|
connection.connectionErrorStream.subscribe((event: CloseEvent) => {
|
||||||
console.log("An error occurred while connecting to socket server. Retrying");
|
console.log("An error occurred while connecting to socket server. Retrying");
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
@ -283,7 +304,7 @@ class ConnectionManager {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onConnect((connect: OnConnectInterface) => {
|
connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => {
|
||||||
resolve(connect);
|
resolve(connect);
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -292,7 +313,7 @@ class ConnectionManager {
|
|||||||
this.reconnectingTimeout = setTimeout(() => {
|
this.reconnectingTimeout = setTimeout(() => {
|
||||||
//todo: allow a way to break recursion?
|
//todo: allow a way to break recursion?
|
||||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||||
this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then(
|
void this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then(
|
||||||
(connection) => resolve(connection)
|
(connection) => resolve(connection)
|
||||||
);
|
);
|
||||||
}, 4000 + Math.floor(Math.random() * 2000));
|
}, 4000 + Math.floor(Math.random() * 2000));
|
||||||
@ -315,15 +336,17 @@ class ConnectionManager {
|
|||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
if (!state || !localUserStore.verifyState(state)) {
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
throw "Could not validate state!";
|
throw new Error("Could not validate state!");
|
||||||
}
|
}
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw "No Auth code provided";
|
throw new Error("No Auth code provided");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
|
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
|
||||||
params: { code, nonce, token, playUri: this.currentRoom?.key },
|
params: { code, nonce, token, playUri: this.currentRoom?.key },
|
||||||
}).then((res) => res.data);
|
}).then((res) => {
|
||||||
|
return res.data;
|
||||||
|
});
|
||||||
localUserStore.setAuthToken(authToken);
|
localUserStore.setAuthToken(authToken);
|
||||||
this.localUser = new LocalUser(userUuid, textures, email);
|
this.localUser = new LocalUser(userUuid, textures, email);
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
|
@ -1,43 +1,12 @@
|
|||||||
import type { SignalData } from "simple-peer";
|
import type { SignalData } from "simple-peer";
|
||||||
import type { RoomConnection } from "./RoomConnection";
|
import type { RoomConnection } from "./RoomConnection";
|
||||||
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
||||||
|
import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
|
||||||
export enum EventMessage {
|
|
||||||
CONNECT = "connect",
|
|
||||||
WEBRTC_SIGNAL = "webrtc-signal",
|
|
||||||
WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal",
|
|
||||||
WEBRTC_START = "webrtc-start",
|
|
||||||
//START_ROOM = "start-room", // From server to client: list of all room users/groups/items
|
|
||||||
JOIN_ROOM = "join-room", // bi-directional
|
|
||||||
USER_POSITION = "user-position", // From client to server
|
|
||||||
USER_MOVED = "user-moved", // From server to client
|
|
||||||
USER_LEFT = "user-left", // From server to client
|
|
||||||
MESSAGE_ERROR = "message-error",
|
|
||||||
WEBRTC_DISCONNECT = "webrtc-disconect",
|
|
||||||
GROUP_CREATE_UPDATE = "group-create-update",
|
|
||||||
GROUP_DELETE = "group-delete",
|
|
||||||
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
|
||||||
ITEM_EVENT = "item-event",
|
|
||||||
|
|
||||||
CONNECT_ERROR = "connect_error",
|
|
||||||
CONNECTING_ERROR = "connecting_error",
|
|
||||||
SET_SILENT = "set_silent", // Set or unset the silent mode for this user.
|
|
||||||
SET_VIEWPORT = "set-viewport",
|
|
||||||
BATCH = "batch",
|
|
||||||
|
|
||||||
PLAY_GLOBAL_MESSAGE = "play-global-message",
|
|
||||||
STOP_GLOBAL_MESSAGE = "stop-global-message",
|
|
||||||
|
|
||||||
TELEPORT = "teleport",
|
|
||||||
USER_MESSAGE = "user-message",
|
|
||||||
START_JITSI_ROOM = "start-jitsi-room",
|
|
||||||
SET_VARIABLE = "set-variable",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PointInterface {
|
export interface PointInterface {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
direction: string;
|
direction: string; // TODO: modify this to the enum from ts-proto
|
||||||
moving: boolean;
|
moving: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +33,7 @@ export interface MessageUserJoined {
|
|||||||
visitCardUrl: string | null;
|
visitCardUrl: string | null;
|
||||||
companion: string | null;
|
companion: string | null;
|
||||||
userUuid: string;
|
userUuid: string;
|
||||||
|
outlineColor: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
@ -102,6 +72,12 @@ export interface ItemEventMessageInterface {
|
|||||||
parameters: unknown;
|
parameters: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlayerDetailsUpdatedMessageInterface {
|
||||||
|
userId: number;
|
||||||
|
outlineColor: number;
|
||||||
|
removeOutlineColor: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RoomJoinedMessageInterface {
|
export interface RoomJoinedMessageInterface {
|
||||||
//users: MessageUserPositionInterface[],
|
//users: MessageUserPositionInterface[],
|
||||||
//groups: GroupCreatedUpdatedMessageInterface[],
|
//groups: GroupCreatedUpdatedMessageInterface[],
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { Subject } from "rxjs";
|
|
||||||
|
|
||||||
interface EmoteEvent {
|
|
||||||
userId: number;
|
|
||||||
emote: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EmoteEventStream {
|
|
||||||
private _stream: Subject<EmoteEvent> = new Subject();
|
|
||||||
public stream = this._stream.asObservable();
|
|
||||||
|
|
||||||
fire(userId: number, emote: string) {
|
|
||||||
this._stream.next({ userId, emote });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const emoteEventStream = new EmoteEventStream();
|
|
@ -14,6 +14,7 @@ const audioPlayerMuteKey = "audioMute";
|
|||||||
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
||||||
const fullscreenKey = "fullscreen";
|
const fullscreenKey = "fullscreen";
|
||||||
const forceCowebsiteTriggerKey = "forceCowebsiteTrigger";
|
const forceCowebsiteTriggerKey = "forceCowebsiteTrigger";
|
||||||
|
const ignoreFollowRequests = "ignoreFollowRequests";
|
||||||
const lastRoomUrl = "lastRoomUrl";
|
const lastRoomUrl = "lastRoomUrl";
|
||||||
const authToken = "authToken";
|
const authToken = "authToken";
|
||||||
const state = "state";
|
const state = "state";
|
||||||
@ -128,13 +129,23 @@ class LocalUserStore {
|
|||||||
return localStorage.getItem(forceCowebsiteTriggerKey) === "true";
|
return localStorage.getItem(forceCowebsiteTriggerKey) === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
setLastRoomUrl(roomUrl: string): void {
|
setIgnoreFollowRequests(value: boolean): void {
|
||||||
|
localStorage.setItem(ignoreFollowRequests, value.toString());
|
||||||
|
}
|
||||||
|
getIgnoreFollowRequests(): boolean {
|
||||||
|
return localStorage.getItem(ignoreFollowRequests) === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLastRoomUrl(roomUrl: string): Promise<void> {
|
||||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||||
if ("caches" in window) {
|
if ("caches" in window) {
|
||||||
caches.open(cacheAPIIndex).then((cache) => {
|
try {
|
||||||
|
const cache = await caches.open(cacheAPIIndex);
|
||||||
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
const stringResponse = new Response(JSON.stringify({ roomUrl }));
|
||||||
cache.put(`/${lastRoomUrl}`, stringResponse);
|
await cache.put(`/${lastRoomUrl}`, stringResponse);
|
||||||
});
|
} catch (e) {
|
||||||
|
console.error("Could not store last room url in Browser cache. Are you using private browser mode?", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getLastRoomUrl(): string {
|
getLastRoomUrl(): string {
|
||||||
|
@ -5,6 +5,8 @@ import type { CharacterTexture } from "./LocalUser";
|
|||||||
import { localUserStore } from "./LocalUserStore";
|
import { localUserStore } from "./LocalUserStore";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { axiosWithRetry } from "./AxiosUtils";
|
import { axiosWithRetry } from "./AxiosUtils";
|
||||||
|
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
|
|
||||||
export class MapDetail {
|
export class MapDetail {
|
||||||
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
|
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
|
||||||
@ -16,7 +18,10 @@ export interface RoomRedirect {
|
|||||||
|
|
||||||
export class Room {
|
export class Room {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
public readonly isPublic: boolean;
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
private readonly isPublic: boolean;
|
||||||
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
|
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
|
||||||
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
||||||
private _mapUrl: string | undefined;
|
private _mapUrl: string | undefined;
|
||||||
@ -25,6 +30,8 @@ export class Room {
|
|||||||
private readonly _search: URLSearchParams;
|
private readonly _search: URLSearchParams;
|
||||||
private _contactPage: string | undefined;
|
private _contactPage: string | undefined;
|
||||||
private _group: string | null = null;
|
private _group: string | null = null;
|
||||||
|
private _expireOn: Date | undefined;
|
||||||
|
private _canReport: boolean = false;
|
||||||
|
|
||||||
private constructor(private roomUrl: URL) {
|
private constructor(private roomUrl: URL) {
|
||||||
this.id = roomUrl.pathname;
|
this.id = roomUrl.pathname;
|
||||||
@ -32,7 +39,7 @@ export class Room {
|
|||||||
if (this.id.startsWith("/")) {
|
if (this.id.startsWith("/")) {
|
||||||
this.id = this.id.substr(1);
|
this.id = this.id.substr(1);
|
||||||
}
|
}
|
||||||
if (this.id.startsWith("_/")) {
|
if (this.id.startsWith("_/") || this.id.startsWith("*/")) {
|
||||||
this.isPublic = true;
|
this.isPublic = true;
|
||||||
} else if (this.id.startsWith("@/")) {
|
} else if (this.id.startsWith("@/")) {
|
||||||
this.isPublic = false;
|
this.isPublic = false;
|
||||||
@ -80,13 +87,11 @@ export class Room {
|
|||||||
const currentRoom = new Room(baseUrl);
|
const currentRoom = new Room(baseUrl);
|
||||||
let instance: string = "global";
|
let instance: string = "global";
|
||||||
if (currentRoom.isPublic) {
|
if (currentRoom.isPublic) {
|
||||||
instance = currentRoom.instance as string;
|
instance = currentRoom.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
||||||
if (absoluteExitSceneUrl.hash) {
|
baseUrl.hash = absoluteExitSceneUrl.hash;
|
||||||
baseUrl.hash = absoluteExitSceneUrl.hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
@ -101,27 +106,41 @@ export class Room {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
if (data.redirectUrl) {
|
|
||||||
return {
|
if (data.authenticationMandatory !== undefined) {
|
||||||
redirectUrl: data.redirectUrl as string,
|
data.authenticationMandatory = Boolean(data.authenticationMandatory);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if (isRoomRedirect(data)) {
|
||||||
|
return {
|
||||||
|
redirectUrl: data.redirectUrl,
|
||||||
|
};
|
||||||
|
} else if (isMapDetailsData(data)) {
|
||||||
|
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||||
|
this._mapUrl = data.mapUrl;
|
||||||
|
this._textures = data.textures;
|
||||||
|
this._group = data.group;
|
||||||
|
this._authenticationMandatory =
|
||||||
|
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
|
||||||
|
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
|
||||||
|
this._contactPage = data.contactPage || CONTACT_URL;
|
||||||
|
if (data.expireOn) {
|
||||||
|
this._expireOn = new Date(data.expireOn);
|
||||||
|
}
|
||||||
|
this._canReport = data.canReport ?? false;
|
||||||
|
return new MapDetail(data.mapUrl, data.textures);
|
||||||
|
} else {
|
||||||
|
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
|
||||||
}
|
}
|
||||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
|
||||||
this._mapUrl = data.mapUrl;
|
|
||||||
this._textures = data.textures;
|
|
||||||
this._group = data.group;
|
|
||||||
this._authenticationMandatory =
|
|
||||||
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
|
|
||||||
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
|
|
||||||
this._contactPage = data.contactPage || CONTACT_URL;
|
|
||||||
return new MapDetail(data.mapUrl, data.textures);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (axios.isAxiosError(e) && e.response?.status == 401 && e.response?.data === "Token decrypted error") {
|
if (axios.isAxiosError(e) && e.response?.status == 401 && e.response?.data === "Token decrypted error") {
|
||||||
console.warn("JWT token sent could not be decrypted. Maybe it expired?");
|
console.warn("JWT token sent could not be decrypted. Maybe it expired?");
|
||||||
localUserStore.setAuthToken(null);
|
localUserStore.setAuthToken(null);
|
||||||
window.location.assign("/login");
|
window.location.assign("/login");
|
||||||
} else {
|
} else if (axios.isAxiosError(e)) {
|
||||||
console.error("Error => getMapDetail", e, e.response);
|
console.error("Error => getMapDetail", e, e.response);
|
||||||
|
} else {
|
||||||
|
console.error("Error => getMapDetail", e);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -131,6 +150,8 @@ export class Room {
|
|||||||
* Instance name is:
|
* Instance name is:
|
||||||
* - In a public URL: the second part of the URL ( _/[instance]/map.json)
|
* - In a public URL: the second part of the URL ( _/[instance]/map.json)
|
||||||
* - In a private URL: [organizationId/worldId]
|
* - In a private URL: [organizationId/worldId]
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public getInstance(): string {
|
public getInstance(): string {
|
||||||
if (this.instance !== undefined) {
|
if (this.instance !== undefined) {
|
||||||
@ -138,7 +159,7 @@ export class Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.isPublic) {
|
if (this.isPublic) {
|
||||||
const match = /_\/([^/]+)\/.+/.exec(this.id);
|
const match = /[_*]\/([^/]+)\/.+/.exec(this.id);
|
||||||
if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
|
if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
|
||||||
this.instance = match[1];
|
this.instance = match[1];
|
||||||
return this.instance;
|
return this.instance;
|
||||||
@ -210,4 +231,12 @@ export class Room {
|
|||||||
get group(): string | null {
|
get group(): string | null {
|
||||||
return this._group;
|
return this._group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get expireOn(): Date | undefined {
|
||||||
|
return this._expireOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canReport(): boolean {
|
||||||
|
return this._canReport;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { Subject } from "rxjs";
|
|
||||||
|
|
||||||
class WorldFullMessageStream {
|
|
||||||
private _stream: Subject<string | null> = new Subject<string | null>();
|
|
||||||
public stream = this._stream.asObservable();
|
|
||||||
|
|
||||||
onMessage(message?: string) {
|
|
||||||
this._stream.next(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const worldFullMessageStream = new WorldFullMessageStream();
|
|
1
front/src/Messages/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/generated/
|
|
2
front/src/Messages/JsonMessages/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
1
front/src/Messages/ts-proto-generated/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*
|
@ -1,21 +1,21 @@
|
|||||||
import { PositionMessage } from "../Messages/generated/messages_pb";
|
import { PositionMessage, PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
|
||||||
import Direction = PositionMessage.Direction;
|
|
||||||
import type { PointInterface } from "../Connexion/ConnexionModels";
|
import type { PointInterface } from "../Connexion/ConnexionModels";
|
||||||
|
|
||||||
export class ProtobufClientUtils {
|
export class ProtobufClientUtils {
|
||||||
public static toPointInterface(position: PositionMessage): PointInterface {
|
public static toPointInterface(position: PositionMessage): PointInterface {
|
||||||
let direction: string;
|
let direction: string;
|
||||||
switch (position.getDirection()) {
|
switch (position.direction) {
|
||||||
case Direction.UP:
|
case PositionMessage_Direction.UP:
|
||||||
direction = "up";
|
direction = "up";
|
||||||
break;
|
break;
|
||||||
case Direction.DOWN:
|
case PositionMessage_Direction.DOWN:
|
||||||
direction = "down";
|
direction = "down";
|
||||||
break;
|
break;
|
||||||
case Direction.LEFT:
|
case PositionMessage_Direction.LEFT:
|
||||||
direction = "left";
|
direction = "left";
|
||||||
break;
|
break;
|
||||||
case Direction.RIGHT:
|
case PositionMessage_Direction.RIGHT:
|
||||||
direction = "right";
|
direction = "right";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -24,10 +24,10 @@ export class ProtobufClientUtils {
|
|||||||
|
|
||||||
// sending to all clients in room except sender
|
// sending to all clients in room except sender
|
||||||
return {
|
return {
|
||||||
x: position.getX(),
|
x: position.x,
|
||||||
y: position.getY(),
|
y: position.y,
|
||||||
direction,
|
direction,
|
||||||
moving: position.getMoving(),
|
moving: position.moving,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,15 @@ export class Companion extends Container {
|
|||||||
this.companionName = name;
|
this.companionName = name;
|
||||||
this._pictureStore = writable(undefined);
|
this._pictureStore = writable(undefined);
|
||||||
|
|
||||||
texturePromise.then((resource) => {
|
texturePromise
|
||||||
this.addResource(resource);
|
.then((resource) => {
|
||||||
this.invisible = false;
|
this.addResource(resource);
|
||||||
return this.getSnapshot().then((htmlImageElementSrc) => {
|
this.invisible = false;
|
||||||
this._pictureStore.set(htmlImageElementSrc);
|
return this.getSnapshot().then((htmlImageElementSrc) => {
|
||||||
});
|
this._pictureStore.set(htmlImageElementSrc);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
|
||||||
this.scene.physics.world.enableBody(this);
|
this.scene.physics.world.enableBody(this);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./Co
|
|||||||
|
|
||||||
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
||||||
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
||||||
lazyLoadCompanionResource(loader, resource.name);
|
lazyLoadCompanionResource(loader, resource.name).catch((e) => console.error(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
return COMPANION_RESOURCES;
|
return COMPANION_RESOURCES;
|
||||||
|
@ -72,9 +72,11 @@ export class Loader {
|
|||||||
if (this.loadingText) {
|
if (this.loadingText) {
|
||||||
this.loadingText.destroy();
|
this.loadingText.destroy();
|
||||||
}
|
}
|
||||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
promiseLoadLogoTexture
|
||||||
resLoadingImage.destroy();
|
.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||||
});
|
resLoadingImage.destroy();
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
this.progress.destroy();
|
this.progress.destroy();
|
||||||
this.progressContainer.destroy();
|
this.progressContainer.destroy();
|
||||||
if (this.scene instanceof DirtyScene) {
|
if (this.scene instanceof DirtyScene) {
|
||||||
|
@ -13,7 +13,8 @@ import { isSilentStore } from "../../Stores/MediaStore";
|
|||||||
import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager";
|
import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager";
|
||||||
import { TexturesHelper } from "../Helpers/TexturesHelper";
|
import { TexturesHelper } from "../Helpers/TexturesHelper";
|
||||||
import type { PictureStore } from "../../Stores/PictureStore";
|
import type { PictureStore } from "../../Stores/PictureStore";
|
||||||
import { Writable, writable } from "svelte/store";
|
import { Unsubscriber, Writable, writable } from "svelte/store";
|
||||||
|
import { createColorStore } from "../../Stores/OutlineColorStore";
|
||||||
|
|
||||||
const playerNameY = -25;
|
const playerNameY = -25;
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ export abstract class Character extends Container {
|
|||||||
private readonly playerName: Text;
|
private readonly playerName: Text;
|
||||||
public PlayerValue: string;
|
public PlayerValue: string;
|
||||||
public sprites: Map<string, Sprite>;
|
public sprites: Map<string, Sprite>;
|
||||||
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||||
//private teleportation: Sprite;
|
//private teleportation: Sprite;
|
||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
@ -40,6 +41,8 @@ export abstract class Character extends Container {
|
|||||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||||
scene: GameScene;
|
scene: GameScene;
|
||||||
private readonly _pictureStore: Writable<string | undefined>;
|
private readonly _pictureStore: Writable<string | undefined>;
|
||||||
|
private readonly outlineColorStore = createColorStore();
|
||||||
|
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
scene: GameScene,
|
scene: GameScene,
|
||||||
@ -97,18 +100,26 @@ export abstract class Character extends Container {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.on("pointerover", () => {
|
this.on("pointerover", () => {
|
||||||
this.getOutlinePlugin()?.add(this.playerName, {
|
this.outlineColorStore.pointerOver();
|
||||||
thickness: 2,
|
|
||||||
outlineColor: 0xffff00,
|
|
||||||
});
|
|
||||||
this.scene.markDirty();
|
|
||||||
});
|
});
|
||||||
this.on("pointerout", () => {
|
this.on("pointerout", () => {
|
||||||
this.getOutlinePlugin()?.remove(this.playerName);
|
this.outlineColorStore.pointerOut();
|
||||||
this.scene.markDirty();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
|
||||||
|
if (color === undefined) {
|
||||||
|
this.getOutlinePlugin()?.remove(this.playerName);
|
||||||
|
} else {
|
||||||
|
this.getOutlinePlugin()?.remove(this.playerName);
|
||||||
|
this.getOutlinePlugin()?.add(this.playerName, {
|
||||||
|
thickness: 2,
|
||||||
|
outlineColor: color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.scene.markDirty();
|
||||||
|
});
|
||||||
|
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
|
|
||||||
this.scene.physics.world.enableBody(this);
|
this.scene.physics.world.enableBody(this);
|
||||||
@ -266,24 +277,20 @@ export abstract class Character extends Container {
|
|||||||
|
|
||||||
body.setVelocity(x, y);
|
body.setVelocity(x, y);
|
||||||
|
|
||||||
// up or down animations are prioritized over left and right
|
if (Math.abs(body.velocity.x) > Math.abs(body.velocity.y)) {
|
||||||
if (body.velocity.y < 0) {
|
if (body.velocity.x < 0) {
|
||||||
//moving up
|
this.lastDirection = PlayerAnimationDirections.Left;
|
||||||
this.lastDirection = PlayerAnimationDirections.Up;
|
} else if (body.velocity.x > 0) {
|
||||||
this.playAnimation(PlayerAnimationDirections.Up, true);
|
this.lastDirection = PlayerAnimationDirections.Right;
|
||||||
} else if (body.velocity.y > 0) {
|
}
|
||||||
//moving down
|
} else {
|
||||||
this.lastDirection = PlayerAnimationDirections.Down;
|
if (body.velocity.y < 0) {
|
||||||
this.playAnimation(PlayerAnimationDirections.Down, true);
|
this.lastDirection = PlayerAnimationDirections.Up;
|
||||||
} else if (body.velocity.x > 0) {
|
} else if (body.velocity.y > 0) {
|
||||||
//moving right
|
this.lastDirection = PlayerAnimationDirections.Down;
|
||||||
this.lastDirection = PlayerAnimationDirections.Right;
|
}
|
||||||
this.playAnimation(PlayerAnimationDirections.Right, true);
|
|
||||||
} else if (body.velocity.x < 0) {
|
|
||||||
//moving left
|
|
||||||
this.lastDirection = PlayerAnimationDirections.Left;
|
|
||||||
this.playAnimation(PlayerAnimationDirections.Left, true);
|
|
||||||
}
|
}
|
||||||
|
this.playAnimation(this.lastDirection, true);
|
||||||
|
|
||||||
this.setDepth(this.y);
|
this.setDepth(this.y);
|
||||||
|
|
||||||
@ -315,6 +322,7 @@ export abstract class Character extends Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.list.forEach((objectContaining) => objectContaining.destroy());
|
this.list.forEach((objectContaining) => objectContaining.destroy());
|
||||||
|
this.outlineColorStoreUnsubscribe();
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,4 +409,12 @@ export abstract class Character extends Container {
|
|||||||
public get pictureStore(): PictureStore {
|
public get pictureStore(): PictureStore {
|
||||||
return this._pictureStore;
|
return this._pictureStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setOutlineColor(color: number): void {
|
||||||
|
this.outlineColorStore.setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeOutlineColor(): void {
|
||||||
|
this.outlineColorStore.removeColor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ export const getRessourceDescriptor = (
|
|||||||
const playerResource = LAYERS[i][textureName];
|
const playerResource = LAYERS[i][textureName];
|
||||||
if (playerResource !== undefined) return playerResource;
|
if (playerResource !== undefined) return playerResource;
|
||||||
}
|
}
|
||||||
throw "Could not find a data for texture " + textureName;
|
throw new Error("Could not find a data for texture " + textureName);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createLoadingPromise = (
|
export const createLoadingPromise = (
|
||||||
|
@ -44,6 +44,7 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
private startFollowTween?: Phaser.Tweens.Tween;
|
private startFollowTween?: Phaser.Tweens.Tween;
|
||||||
|
|
||||||
private playerToFollow?: Player;
|
private playerToFollow?: Player;
|
||||||
|
private cameraLocked: boolean;
|
||||||
|
|
||||||
constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) {
|
constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) {
|
||||||
super();
|
super();
|
||||||
@ -51,6 +52,7 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
|
|
||||||
this.camera = scene.cameras.main;
|
this.camera = scene.cameras.main;
|
||||||
this.cameraBounds = cameraBounds;
|
this.cameraBounds = cameraBounds;
|
||||||
|
this.cameraLocked = false;
|
||||||
|
|
||||||
this.waScaleManager = waScaleManager;
|
this.waScaleManager = waScaleManager;
|
||||||
|
|
||||||
@ -123,6 +125,8 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
this.waScaleManager.saveZoom();
|
this.waScaleManager.saveZoom();
|
||||||
this.waScaleManager.setFocusTarget(focusOn);
|
this.waScaleManager.setFocusTarget(focusOn);
|
||||||
|
|
||||||
|
this.cameraLocked = true;
|
||||||
|
this.unlockCameraWithDelay(duration);
|
||||||
this.restoreZoomTween?.stop();
|
this.restoreZoomTween?.stop();
|
||||||
this.startFollowTween?.stop();
|
this.startFollowTween?.stop();
|
||||||
const marginMult = 1 + margin;
|
const marginMult = 1 + margin;
|
||||||
@ -143,8 +147,9 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public leaveFocusMode(player: Player, duration: number = 1000): void {
|
public leaveFocusMode(player: Player, duration = 0): void {
|
||||||
this.waScaleManager.setFocusTarget();
|
this.waScaleManager.setFocusTarget();
|
||||||
|
this.unlockCameraWithDelay(duration);
|
||||||
this.startFollowPlayer(player, duration);
|
this.startFollowPlayer(player, duration);
|
||||||
this.restoreZoom(duration);
|
this.restoreZoom(duration);
|
||||||
}
|
}
|
||||||
@ -196,8 +201,14 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isCameraZoomLocked(): boolean {
|
public isCameraLocked(): boolean {
|
||||||
return [CameraMode.Focus, CameraMode.Positioned].includes(this.cameraMode);
|
return this.cameraLocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unlockCameraWithDelay(delay: number): void {
|
||||||
|
this.scene.time.delayedCall(delay, () => {
|
||||||
|
this.cameraLocked = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCameraMode(mode: CameraMode): void {
|
private setCameraMode(mode: CameraMode): void {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { emoteEventStream } from "../../Connexion/EmoteEventStream";
|
|
||||||
import type { GameScene } from "./GameScene";
|
import type { GameScene } from "./GameScene";
|
||||||
import type { Subscription } from "rxjs";
|
import type { Subscription } from "rxjs";
|
||||||
|
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
||||||
|
|
||||||
export class EmoteManager {
|
export class EmoteManager {
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
constructor(private scene: GameScene) {
|
constructor(private scene: GameScene, private connection: RoomConnection) {
|
||||||
this.subscription = emoteEventStream.stream.subscribe((event) => {
|
this.subscription = connection.emoteEventMessageStream.subscribe((event) => {
|
||||||
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
const actor = this.scene.MapPlayersByKey.get(event.actorUserId);
|
||||||
if (actor) {
|
if (actor) {
|
||||||
actor.playEmote(event.emote);
|
actor.playEmote(event.emote);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export class GameManager {
|
|||||||
|
|
||||||
getCharacterLayers(): string[] {
|
getCharacterLayers(): string[] {
|
||||||
if (!this.characterLayers) {
|
if (!this.characterLayers) {
|
||||||
throw "characterLayers are not set";
|
throw new Error("characterLayers are not set");
|
||||||
}
|
}
|
||||||
return this.characterLayers;
|
return this.characterLayers;
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ export class GameManager {
|
|||||||
* This will close the socket connections and stop the gameScene, but won't remove it.
|
* This will close the socket connections and stop the gameScene, but won't remove it.
|
||||||
*/
|
*/
|
||||||
leaveGame(targetSceneName: string, sceneClass: Phaser.Scene): void {
|
leaveGame(targetSceneName: string, sceneClass: Phaser.Scene): void {
|
||||||
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
if (this.currentGameSceneName === null) throw new Error("No current scene id set!");
|
||||||
const gameScene: GameScene = this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
const gameScene: GameScene = this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
||||||
gameScene.cleanupClosingScene();
|
gameScene.cleanupClosingScene();
|
||||||
gameScene.createSuccessorGameScene(false, false);
|
gameScene.createSuccessorGameScene(false, false);
|
||||||
@ -143,7 +143,7 @@ export class GameManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentGameScene(): GameScene {
|
public getCurrentGameScene(): GameScene {
|
||||||
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
if (this.currentGameSceneName === null) throw new Error("No current scene id set!");
|
||||||
return this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
return this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,14 @@ export class GameMap {
|
|||||||
let depth = -2;
|
let depth = -2;
|
||||||
for (const layer of this.flatLayers) {
|
for (const layer of this.flatLayers) {
|
||||||
if (layer.type === "tilelayer") {
|
if (layer.type === "tilelayer") {
|
||||||
this.phaserLayers.push(phaserMap.createLayer(layer.name, terrains, 0, 0).setDepth(depth));
|
this.phaserLayers.push(
|
||||||
|
phaserMap
|
||||||
|
.createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32)
|
||||||
|
.setDepth(depth)
|
||||||
|
.setAlpha(layer.opacity)
|
||||||
|
.setVisible(layer.visible)
|
||||||
|
.setSize(layer.width, layer.height)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
||||||
depth = DEPTH_OVERLAY_INDEX;
|
depth = DEPTH_OVERLAY_INDEX;
|
||||||
|
@ -123,7 +123,7 @@ export class GameMapPropertiesListener {
|
|||||||
.then((coWebsite) => {
|
.then((coWebsite) => {
|
||||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
||||||
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
|
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
|
||||||
coWebsiteManager.closeCoWebsite(coWebsite);
|
coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => console.error(e));
|
||||||
this.coWebsitesOpenByLayer.delete(layer);
|
this.coWebsitesOpenByLayer.delete(layer);
|
||||||
this.coWebsitesActionTriggerByLayer.delete(layer);
|
this.coWebsitesActionTriggerByLayer.delete(layer);
|
||||||
} else {
|
} else {
|
||||||
@ -132,7 +132,8 @@ export class GameMapPropertiesListener {
|
|||||||
state: OpenCoWebsiteState.OPENED,
|
state: OpenCoWebsiteState.OPENED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
|
||||||
layoutManagerActionStore.removeAction(actionUuid);
|
layoutManagerActionStore.removeAction(actionUuid);
|
||||||
};
|
};
|
||||||
@ -198,7 +199,7 @@ export class GameMapPropertiesListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (coWebsiteOpen.coWebsite !== undefined) {
|
if (coWebsiteOpen.coWebsite !== undefined) {
|
||||||
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite);
|
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coWebsitesOpenByLayer.delete(layer);
|
this.coWebsitesOpenByLayer.delete(layer);
|
||||||
|