Merge branch 'develop' of github.com:thecodingmachine/workadventure into audioPlayerImprovements
2
.github/workflows/build-and-deploy.yml
vendored
@ -199,4 +199,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
|
msg: "Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re \nTests available at https://maps-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re/tests"
|
||||||
|
13
.github/workflows/push-to-npm.yml
vendored
@ -2,6 +2,7 @@ name: Push @workadventure/iframe-api-typings to NPM
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
push:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -13,10 +14,6 @@ jobs:
|
|||||||
node-version: '14.x'
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Edit tsconfig.json to add declarations
|
|
||||||
run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json"
|
|
||||||
working-directory: "front"
|
|
||||||
|
|
||||||
- name: Replace version number
|
- name: Replace version number
|
||||||
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
|
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
|
||||||
working-directory: "front/packages/iframe-api-typings"
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
@ -47,15 +44,18 @@ jobs:
|
|||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: yarn run build
|
run: yarn run build-typings
|
||||||
env:
|
env:
|
||||||
API_URL: "localhost:8080"
|
PUSHER_URL: "//localhost:8080"
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
||||||
- name: Copy typings to package dir
|
- name: Copy typings to package dir
|
||||||
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
||||||
|
|
||||||
|
- name: Copy typings to package dir (2)
|
||||||
|
run: cp -R front/dist/src/Api front/packages/iframe-api-typings/Api
|
||||||
|
|
||||||
- name: Install dependencies in package
|
- name: Install dependencies in package
|
||||||
run: yarn install
|
run: yarn install
|
||||||
working-directory: "front/packages/iframe-api-typings"
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
@ -65,3 +65,4 @@ jobs:
|
|||||||
working-directory: "front/packages/iframe-api-typings"
|
working-directory: "front/packages/iframe-api-typings"
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
3
.gitignore
vendored
@ -7,4 +7,5 @@ docker-compose.override.yaml
|
|||||||
maps/yarn.lock
|
maps/yarn.lock
|
||||||
maps/dist/computer.js
|
maps/dist/computer.js
|
||||||
maps/dist/computer.js.map
|
maps/dist/computer.js.map
|
||||||
/node_modules/
|
node_modules
|
||||||
|
_
|
28
CHANGELOG.md
@ -10,11 +10,35 @@
|
|||||||
- New scripting API features :
|
- New scripting API features :
|
||||||
- Use `WA.room.showLayer(): void` to show a layer
|
- Use `WA.room.showLayer(): void` to show a layer
|
||||||
- Use `WA.room.hideLayer(): void` to hide a layer
|
- Use `WA.room.hideLayer(): void` to hide a layer
|
||||||
- Use `WA.room.setProperty() : void` to add or change existing property of a layer
|
- Use `WA.room.setProperty() : void` to add, delete or change existing property of a layer
|
||||||
- Use `WA.player.onPlayerMove(): void` to track the movement of the current player
|
- Use `WA.player.onPlayerMove(): void` to track the movement of the current player
|
||||||
- Use `WA.room.getCurrentUser(): Promise<User>` to get the ID, name and tags of the current player
|
- Use `WA.player.getCurrentUser(): Promise<User>` to get the ID, name and tags of the current player
|
||||||
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
||||||
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
||||||
|
- Use `WA.room.setTiles(): void` to add, delete or change an array of tiles
|
||||||
|
- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked.
|
||||||
|
- The text chat was redesigned to be prettier and to use more features :
|
||||||
|
- The chat is now persistent bewteen discussions and always accesible
|
||||||
|
- The chat now tracks incoming and outcoming users in your conversation
|
||||||
|
- The chat allows your to see the visit card of users
|
||||||
|
- You can close the chat window with the escape key
|
||||||
|
- Added a 'Enable notifications' button in the menu.
|
||||||
|
- The exchange format between Pusher and Admin servers has changed. If you have your own implementation of an admin server, these endpoints signatures have changed:
|
||||||
|
- `/api/map`: now accepts a complete room URL instead of organization/world/room slugs
|
||||||
|
- `/api/ban`: new endpoint to report users
|
||||||
|
- as a side effect, the "routing" is now completely stored on the admin side, so by implementing your own admin server, you can develop completely custom routing
|
||||||
|
|
||||||
|
## Version 1.4.3 - 1.4.4 - 1.4.5
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fixing the generation of @workadventure/iframe-api-typings
|
||||||
|
|
||||||
|
## Version 1.4.2
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
- A script in an iframe opened by another script can use the IFrame API.
|
||||||
|
|
||||||
## Version 1.4.1
|
## Version 1.4.1
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ Before committing, be sure to install the "Prettier" precommit hook that will re
|
|||||||
In order to enable the "Prettier" precommit hook, at the root of the project, run:
|
In order to enable the "Prettier" precommit hook, at the root of the project, run:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ yarn run install
|
$ yarn install
|
||||||
$ yarn run prepare
|
$ yarn run prepare
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export class DebugController {
|
|||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.status(401).send("Invalid token sent!");
|
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -5,8 +5,6 @@ import { PositionInterface } from "_Model/PositionInterface";
|
|||||||
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone";
|
import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone";
|
||||||
import { PositionNotifier } from "./PositionNotifier";
|
import { PositionNotifier } from "./PositionNotifier";
|
||||||
import { Movable } from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier";
|
|
||||||
import { arrayIntersect } from "../Services/ArrayHelper";
|
|
||||||
import { EmoteEventMessage, JoinRoomMessage } from "../Messages/generated/messages_pb";
|
import { EmoteEventMessage, JoinRoomMessage } from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { ZoneSocket } from "src/RoomManager";
|
import { ZoneSocket } from "src/RoomManager";
|
||||||
@ -15,12 +13,6 @@ import { Admin } from "../Model/Admin";
|
|||||||
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;
|
||||||
|
|
||||||
export enum GameRoomPolicyTypes {
|
|
||||||
ANONYMOUS_POLICY = 1,
|
|
||||||
MEMBERS_ONLY_POLICY,
|
|
||||||
USE_TAGS_POLICY,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GameRoom {
|
export class GameRoom {
|
||||||
private readonly minDistance: number;
|
private readonly minDistance: number;
|
||||||
private readonly groupRadius: number;
|
private readonly groupRadius: number;
|
||||||
@ -37,15 +29,12 @@ export class GameRoom {
|
|||||||
private itemsState: Map<number, unknown> = new Map<number, unknown>();
|
private itemsState: Map<number, unknown> = new Map<number, unknown>();
|
||||||
|
|
||||||
private readonly positionNotifier: PositionNotifier;
|
private readonly positionNotifier: PositionNotifier;
|
||||||
public readonly roomId: string;
|
public readonly roomUrl: string;
|
||||||
public readonly roomSlug: string;
|
|
||||||
public readonly worldSlug: string = "";
|
|
||||||
public readonly organizationSlug: string = "";
|
|
||||||
private versionNumber: number = 1;
|
private versionNumber: number = 1;
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
roomId: string,
|
roomUrl: string,
|
||||||
connectCallback: ConnectCallback,
|
connectCallback: ConnectCallback,
|
||||||
disconnectCallback: DisconnectCallback,
|
disconnectCallback: DisconnectCallback,
|
||||||
minDistance: number,
|
minDistance: number,
|
||||||
@ -55,16 +44,7 @@ export class GameRoom {
|
|||||||
onLeaves: LeavesCallback,
|
onLeaves: LeavesCallback,
|
||||||
onEmote: EmoteCallback
|
onEmote: EmoteCallback
|
||||||
) {
|
) {
|
||||||
this.roomId = roomId;
|
this.roomUrl = roomUrl;
|
||||||
|
|
||||||
if (isRoomAnonymous(roomId)) {
|
|
||||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
|
||||||
} else {
|
|
||||||
const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId);
|
|
||||||
this.roomSlug = roomSlug;
|
|
||||||
this.organizationSlug = organizationSlug;
|
|
||||||
this.worldSlug = worldSlug;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.users = new Map<number, User>();
|
this.users = new Map<number, User>();
|
||||||
this.usersByUuid = new Map<string, User>();
|
this.usersByUuid = new Map<string, User>();
|
||||||
@ -183,7 +163,7 @@ export class GameRoom {
|
|||||||
} else {
|
} else {
|
||||||
const closestUser: User = closestItem;
|
const closestUser: User = closestItem;
|
||||||
const group: Group = new Group(
|
const group: Group = new Group(
|
||||||
this.roomId,
|
this.roomUrl,
|
||||||
[user, closestUser],
|
[user, closestUser],
|
||||||
this.connectCallback,
|
this.connectCallback,
|
||||||
this.disconnectCallback,
|
this.disconnectCallback,
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
//helper functions to parse room IDs
|
|
||||||
|
|
||||||
export const isRoomAnonymous = (roomID: string): boolean => {
|
|
||||||
if (roomID.startsWith("_/")) {
|
|
||||||
return true;
|
|
||||||
} else if (roomID.startsWith("@/")) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
throw new Error("Incorrect room ID: " + roomID);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const extractRoomSlugPublicRoomId = (roomId: string): string => {
|
|
||||||
const idParts = roomId.split("/");
|
|
||||||
if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId);
|
|
||||||
return idParts.slice(2).join("/");
|
|
||||||
};
|
|
||||||
export interface extractDataFromPrivateRoomIdResponse {
|
|
||||||
organizationSlug: string;
|
|
||||||
worldSlug: string;
|
|
||||||
roomSlug: string;
|
|
||||||
}
|
|
||||||
export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => {
|
|
||||||
const idParts = roomId.split("/");
|
|
||||||
if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId);
|
|
||||||
const organizationSlug = idParts[1];
|
|
||||||
const worldSlug = idParts[2];
|
|
||||||
const roomSlug = idParts[3];
|
|
||||||
return { organizationSlug, worldSlug, roomSlug };
|
|
||||||
};
|
|
@ -250,12 +250,12 @@ export class SocketManager {
|
|||||||
//user leave previous world
|
//user leave previous world
|
||||||
room.leave(user);
|
room.leave(user);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(room.roomId);
|
this.rooms.delete(room.roomUrl);
|
||||||
gaugeManager.decNbRoomGauge();
|
gaugeManager.decNbRoomGauge();
|
||||||
debug('Room is empty. Deleting room "%s"', room.roomId);
|
debug('Room is empty. Deleting room "%s"', room.roomUrl);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
clientEventsEmitter.emitClientLeave(user.uuid, room.roomUrl);
|
||||||
console.log("A user left");
|
console.log("A user left");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,6 +308,7 @@ export class SocketManager {
|
|||||||
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.setName(thing.name);
|
userJoinedZoneMessage.setName(thing.name);
|
||||||
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||||
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||||
@ -425,7 +426,6 @@ export class SocketManager {
|
|||||||
// Let's send 2 messages: one to the user joining the group and one to the other user
|
// Let's send 2 messages: one to the user joining the group and one to the other user
|
||||||
const webrtcStartMessage1 = new WebRtcStartMessage();
|
const webrtcStartMessage1 = new WebRtcStartMessage();
|
||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
webrtcStartMessage1.setName(otherUser.name);
|
|
||||||
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, TURN_STATIC_AUTH_SECRET);
|
||||||
@ -436,14 +436,10 @@ export class SocketManager {
|
|||||||
const serverToClientMessage1 = new ServerToClientMessage();
|
const serverToClientMessage1 = new ServerToClientMessage();
|
||||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
||||||
|
|
||||||
//if (!user.socket.disconnecting) {
|
|
||||||
user.socket.write(serverToClientMessage1);
|
user.socket.write(serverToClientMessage1);
|
||||||
//console.log('Sending webrtcstart initiator to '+user.socket.userId)
|
|
||||||
//}
|
|
||||||
|
|
||||||
const webrtcStartMessage2 = new WebRtcStartMessage();
|
const webrtcStartMessage2 = new WebRtcStartMessage();
|
||||||
webrtcStartMessage2.setUserid(user.id);
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
webrtcStartMessage2.setName(user.name);
|
|
||||||
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, TURN_STATIC_AUTH_SECRET);
|
||||||
@ -454,10 +450,7 @@ export class SocketManager {
|
|||||||
const serverToClientMessage2 = new ServerToClientMessage();
|
const serverToClientMessage2 = new ServerToClientMessage();
|
||||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
||||||
|
|
||||||
//if (!otherUser.socket.disconnecting) {
|
|
||||||
otherUser.socket.write(serverToClientMessage2);
|
otherUser.socket.write(serverToClientMessage2);
|
||||||
//console.log('Sending webrtcstart to '+otherUser.socket.userId)
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,6 +607,7 @@ export class SocketManager {
|
|||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
const userJoinedMessage = new UserJoinedZoneMessage();
|
const userJoinedMessage = new UserJoinedZoneMessage();
|
||||||
userJoinedMessage.setUserid(thing.id);
|
userJoinedMessage.setUserid(thing.id);
|
||||||
|
userJoinedMessage.setUseruuid(thing.uuid);
|
||||||
userJoinedMessage.setName(thing.name);
|
userJoinedMessage.setName(thing.name);
|
||||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||||
@ -664,9 +658,9 @@ export class SocketManager {
|
|||||||
public leaveAdminRoom(room: GameRoom, admin: Admin) {
|
public leaveAdminRoom(room: GameRoom, admin: Admin) {
|
||||||
room.adminLeave(admin);
|
room.adminLeave(admin);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(room.roomId);
|
this.rooms.delete(room.roomUrl);
|
||||||
gaugeManager.decNbRoomGauge();
|
gaugeManager.decNbRoomGauge();
|
||||||
debug('Room is empty. Deleting room "%s"', room.roomId);
|
debug('Room is empty. Deleting room "%s"', room.roomUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "../src/Model/RoomIdentifier";
|
|
||||||
|
|
||||||
describe("RoomIdentifier", () => {
|
|
||||||
it("should flag public id as anonymous", () => {
|
|
||||||
expect(isRoomAnonymous('_/global/test')).toBe(true);
|
|
||||||
});
|
|
||||||
it("should flag public id as not anonymous", () => {
|
|
||||||
expect(isRoomAnonymous('@/afup/afup2020/1floor')).toBe(false);
|
|
||||||
});
|
|
||||||
it("should extract roomSlug from public ID", () => {
|
|
||||||
expect(extractRoomSlugPublicRoomId('_/global/npeguin/test.json')).toBe('npeguin/test.json');
|
|
||||||
});
|
|
||||||
it("should extract correct from private ID", () => {
|
|
||||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId('@/afup/afup2020/1floor');
|
|
||||||
expect(organizationSlug).toBe('afup');
|
|
||||||
expect(worldSlug).toBe('afup2020');
|
|
||||||
expect(roomSlug).toBe('1floor');
|
|
||||||
});
|
|
||||||
})
|
|
@ -52,11 +52,11 @@ WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
|
|||||||
### Opening/closing a web page in an iFrame
|
### Opening/closing a web page in an iFrame
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.nav.openCoWebSite(url: string): void
|
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void
|
||||||
WA.nav.closeCoWebSite(): void
|
WA.nav.closeCoWebSite(): void
|
||||||
```
|
```
|
||||||
|
|
||||||
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame.
|
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -65,4 +65,3 @@ WA.nav.openCoWebSite('https://www.wikipedia.org/');
|
|||||||
// ...
|
// ...
|
||||||
WA.nav.closeCoWebSite();
|
WA.nav.closeCoWebSite();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{.section-title.accent.text-primary}
|
{.section-title.accent.text-primary}
|
||||||
# API Player functions Reference
|
# API Player functions Reference
|
||||||
|
|
||||||
|
|
||||||
### Listen to player movement
|
### Listen to player movement
|
||||||
```
|
```
|
||||||
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
||||||
|
@ -9,4 +9,4 @@
|
|||||||
- [Sound functions](api-sound.md)
|
- [Sound functions](api-sound.md)
|
||||||
- [Controls functions](api-controls.md)
|
- [Controls functions](api-controls.md)
|
||||||
|
|
||||||
- [List of deprecated functions](api-deprecated.md)
|
- [List of deprecated functions](api-deprecated.md)
|
||||||
|
@ -54,6 +54,7 @@ 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.
|
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
|
||||||
@ -70,45 +71,43 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s
|
|||||||
|
|
||||||
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 :
|
||||||
|
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/');
|
||||||
```
|
```
|
||||||
|
|
||||||
### Getting information on the current room
|
### Changing tiles
|
||||||
```
|
```
|
||||||
WA.room.getCurrentRoom(): Promise<Room>
|
WA.room.setTiles(tiles: TileDescriptor[]): void
|
||||||
```
|
```
|
||||||
Return a promise that resolves to a `Room` object with the following attributes :
|
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
|
||||||
* **id (string) :** ID of the current room
|
|
||||||
* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called.
|
|
||||||
* **mapUrl (string) :** Url of the JSON map file
|
|
||||||
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
|
||||||
|
|
||||||
Example :
|
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<img src="https://workadventu.re/img/docs/nameIndexProperty.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`TileDescriptor` has the following attributes :
|
||||||
|
* **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.
|
||||||
|
* **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.
|
||||||
|
|
||||||
|
**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`.
|
||||||
|
|
||||||
|
Example :
|
||||||
```javascript
|
```javascript
|
||||||
WA.room.getCurrentRoom((room) => {
|
WA.room.setTiles([
|
||||||
if (room.id === '42') {
|
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
|
||||||
console.log(room.map);
|
{x: 7, y: 4, tile: 109, layer: 'setTiles'},
|
||||||
window.open(room.mapUrl, '_blank');
|
{x: 8, y: 4, tile: 109, layer: 'setTiles'},
|
||||||
}
|
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
||||||
})
|
]);
|
||||||
```
|
|
||||||
|
|
||||||
### Getting information on the current user
|
|
||||||
```
|
|
||||||
WA.player.getCurrentUser(): Promise<User>
|
|
||||||
```
|
|
||||||
Return a promise that resolves to a `User` object with the following attributes :
|
|
||||||
* **id (string) :** ID of the current user
|
|
||||||
* **nickName (string) :** name displayed above the current user
|
|
||||||
* **tags (string[]) :** list of all the tags of the current user
|
|
||||||
|
|
||||||
Example :
|
|
||||||
```javascript
|
|
||||||
WA.room.getCurrentUser().then((user) => {
|
|
||||||
if (user.nickName === 'ABC') {
|
|
||||||
console.log(user.tags);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
3
front/dist/index.tmpl.html
vendored
@ -37,8 +37,7 @@
|
|||||||
<div class="main-container" id="main-container">
|
<div class="main-container" id="main-container">
|
||||||
<!-- Create the editor container -->
|
<!-- Create the editor container -->
|
||||||
<div id="game" class="game">
|
<div id="game" class="game">
|
||||||
<div id="svelte-overlay">
|
<div id="svelte-overlay"></div>
|
||||||
</div>
|
|
||||||
<div id="game-overlay" class="game-overlay">
|
<div id="game-overlay" class="game-overlay">
|
||||||
<div id="main-section" class="main-section">
|
<div id="main-section" class="main-section">
|
||||||
</div>
|
</div>
|
||||||
|
3
front/dist/resources/html/gameMenu.html
vendored
@ -57,6 +57,9 @@
|
|||||||
<section>
|
<section>
|
||||||
<button id="toggleFullscreen">Toggle fullscreen</button>
|
<button id="toggleFullscreen">Toggle fullscreen</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<button id="enableNotification">Enable notifications</button>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button id="sparkButton">Create map</button>
|
<button id="sparkButton">Create map</button>
|
||||||
</section>
|
</section>
|
||||||
|
BIN
front/dist/resources/logos/tcm_full.png
vendored
Before Width: | Height: | Size: 300 B |
BIN
front/dist/resources/logos/tcm_short.png
vendored
Before Width: | Height: | Size: 216 B |
53
front/dist/resources/service-worker.js
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
let CACHE_NAME = 'workavdenture-cache-v1';
|
||||||
|
let urlsToCache = [
|
||||||
|
'/'
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener('install', function(event) {
|
||||||
|
// Perform install steps
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(function(cache) {
|
||||||
|
console.log('Opened cache');
|
||||||
|
return cache.addAll(urlsToCache);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', function(event) {
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request)
|
||||||
|
.then(function(response) {
|
||||||
|
// Cache hit - return response
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(event.request).then(
|
||||||
|
function(response) {
|
||||||
|
// Check if we received a valid response
|
||||||
|
if(!response || response.status !== 200 || response.type !== 'basic') {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: Clone the response. A response is a stream
|
||||||
|
// and because we want the browser to consume the response
|
||||||
|
// as well as the cache consuming the response, we need
|
||||||
|
// to clone it so we have two streams.
|
||||||
|
var responseToCache = response.clone();
|
||||||
|
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(function(cache) {
|
||||||
|
cache.put(event.request, responseToCache);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', function(event) {
|
||||||
|
//TODO activate service worker
|
||||||
|
});
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 880 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 933 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 978 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
front/dist/static/images/favicons/apple-icon.png
vendored
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
front/dist/static/images/favicons/favicon-16x16.png
vendored
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 713 B |
BIN
front/dist/static/images/favicons/favicon-32x32.png
vendored
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 848 B |
BIN
front/dist/static/images/favicons/favicon-96x96.png
vendored
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
front/dist/static/images/favicons/favicon.ico
vendored
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/favicons/icon-512x512.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
11
front/dist/static/images/favicons/manifest.json
vendored
@ -119,7 +119,13 @@
|
|||||||
"src": "/static/images/favicons/android-icon-192x192.png",
|
"src": "/static/images/favicons/android-icon-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image\/png",
|
"type": "image\/png",
|
||||||
"density": "4.0"
|
"density": "4.0",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/images/favicons/icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image\/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
@ -127,6 +133,7 @@
|
|||||||
"display_override": ["window-control-overlay", "minimal-ui"],
|
"display_override": ["window-control-overlay", "minimal-ui"],
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
|
"lang": "en",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"shortcuts": [
|
"shortcuts": [
|
||||||
{
|
{
|
||||||
@ -134,7 +141,7 @@
|
|||||||
"short_name": "WA",
|
"short_name": "WA",
|
||||||
"description": "WorkAdventure application",
|
"description": "WorkAdventure application",
|
||||||
"url": "/",
|
"url": "/",
|
||||||
"icons": [{ "src": "/static/images/favicons/android-icon-192x192.png", "sizes": "192x192" }]
|
"icons": [{ "src": "/static/images/favicons/android-icon-192x192.png", "sizes": "192x192", "type": "image/png" }]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "WorkAdventure application",
|
"description": "WorkAdventure application",
|
||||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
front/dist/static/images/favicons/ms-icon-70x70.png
vendored
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/send.png
vendored
Normal file
After Width: | Height: | Size: 8.3 KiB |
@ -39,7 +39,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/press-start-2p": "^4.3.0",
|
"@fontsource/press-start-2p": "^4.3.0",
|
||||||
"@types/simple-peer": "^9.6.0",
|
"@types/simple-peer": "^9.11.1",
|
||||||
"@types/socket.io-client": "^1.4.32",
|
"@types/socket.io-client": "^1.4.32",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"queue-typescript": "^1.0.1",
|
"queue-typescript": "^1.0.1",
|
||||||
"quill": "1.3.6",
|
"quill": "1.3.6",
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "^6.6.3",
|
||||||
"simple-peer": "^9.6.2",
|
"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"
|
||||||
},
|
},
|
||||||
@ -60,7 +60,8 @@
|
|||||||
"templater": "cross-env ./templater.sh",
|
"templater": "cross-env ./templater.sh",
|
||||||
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||||
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||||
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
"build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack",
|
||||||
|
"test": "cross-env TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||||
"precommit": "lint-staged",
|
"precommit": "lint-staged",
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isDataLayerEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isDataLayerEvent =
|
data: tg.isObject,
|
||||||
new tg.IsInterface().withProperties({
|
})
|
||||||
data: tg.isObject
|
.get();
|
||||||
}).get();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers
|
* A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers
|
||||||
*/
|
*/
|
||||||
export type DataLayerEvent = tg.GuardedType<typeof isDataLayerEvent>;
|
export type DataLayerEvent = tg.GuardedType<typeof isDataLayerEvent>;
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isGameStateEvent =
|
export const isGameStateEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
roomId: tg.isString,
|
roomId: tg.isString,
|
||||||
mapUrl: tg.isString,
|
mapUrl: tg.isString,
|
||||||
nickname: tg.isUnion(tg.isString, tg.isNull),
|
nickname: tg.isUnion(tg.isString, tg.isNull),
|
||||||
uuid: tg.isUnion(tg.isString, tg.isUndefined),
|
uuid: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
||||||
tags : tg.isArray(tg.isString),
|
tags: tg.isArray(tg.isString),
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame when the gameState is received by the script
|
* A message sent from the game to the iFrame when the gameState is received by the script
|
||||||
*/
|
*/
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isHasPlayerMovedEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isHasPlayerMovedEvent =
|
direction: tg.isElementOf("right", "left", "up", "down"),
|
||||||
new tg.IsInterface().withProperties({
|
|
||||||
direction: tg.isElementOf('right', 'left', 'up', 'down'),
|
|
||||||
moving: tg.isBoolean,
|
moving: tg.isBoolean,
|
||||||
x: tg.isNumber,
|
x: tg.isNumber,
|
||||||
y: tg.isNumber
|
y: tg.isNumber,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame to notify a movement from the current player.
|
* A message sent from the game to the iFrame to notify a movement from the current player.
|
||||||
*/
|
*/
|
||||||
export type HasPlayerMovedEvent = tg.GuardedType<typeof isHasPlayerMovedEvent>;
|
export type HasPlayerMovedEvent = tg.GuardedType<typeof isHasPlayerMovedEvent>;
|
||||||
|
|
||||||
|
export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void;
|
||||||
export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void
|
|
||||||
|
@ -1,73 +1,73 @@
|
|||||||
|
import type { GameStateEvent } from "./GameStateEvent";
|
||||||
import type { GameStateEvent } from './GameStateEvent';
|
import type { ButtonClickedEvent } from "./ButtonClickedEvent";
|
||||||
import type { ButtonClickedEvent } from './ButtonClickedEvent';
|
import type { ChatEvent } from "./ChatEvent";
|
||||||
import type { ChatEvent } from './ChatEvent';
|
import type { ClosePopupEvent } from "./ClosePopupEvent";
|
||||||
import type { ClosePopupEvent } from './ClosePopupEvent';
|
import type { EnterLeaveEvent } from "./EnterLeaveEvent";
|
||||||
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
import type { GoToPageEvent } from "./GoToPageEvent";
|
||||||
import type { GoToPageEvent } from './GoToPageEvent';
|
import type { LoadPageEvent } from "./LoadPageEvent";
|
||||||
import type { LoadPageEvent } from './LoadPageEvent';
|
import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent";
|
||||||
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
import type { OpenPopupEvent } from "./OpenPopupEvent";
|
||||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
import type { OpenTabEvent } from "./OpenTabEvent";
|
||||||
import type { OpenTabEvent } from './OpenTabEvent';
|
import type { UserInputChatEvent } from "./UserInputChatEvent";
|
||||||
import type { UserInputChatEvent } from './UserInputChatEvent';
|
|
||||||
import type { DataLayerEvent } from "./DataLayerEvent";
|
import type { DataLayerEvent } from "./DataLayerEvent";
|
||||||
import type { LayerEvent } from './LayerEvent';
|
import type { LayerEvent } from "./LayerEvent";
|
||||||
import type { SetPropertyEvent } from "./setPropertyEvent";
|
import type { SetPropertyEvent } from "./setPropertyEvent";
|
||||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||||
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
||||||
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||||
import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent';
|
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
||||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||||
|
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List event types sent from an iFrame to WorkAdventure
|
||||||
|
*/
|
||||||
export type IframeEventMap = {
|
export type IframeEventMap = {
|
||||||
//getState: GameStateEvent,
|
loadPage: LoadPageEvent;
|
||||||
// updateTile: UpdateTileEvent
|
chat: ChatEvent;
|
||||||
loadPage: LoadPageEvent
|
openPopup: OpenPopupEvent;
|
||||||
chat: ChatEvent,
|
closePopup: ClosePopupEvent;
|
||||||
openPopup: OpenPopupEvent
|
openTab: OpenTabEvent;
|
||||||
closePopup: ClosePopupEvent
|
goToPage: GoToPageEvent;
|
||||||
openTab: OpenTabEvent
|
openCoWebSite: OpenCoWebSiteEvent;
|
||||||
goToPage: GoToPageEvent
|
closeCoWebSite: null;
|
||||||
openCoWebSite: OpenCoWebSiteEvent
|
disablePlayerControls: null;
|
||||||
closeCoWebSite: null
|
restorePlayerControls: null;
|
||||||
disablePlayerControls: null
|
displayBubble: null;
|
||||||
restorePlayerControls: null
|
removeBubble: null;
|
||||||
displayBubble: null
|
onPlayerMove: undefined;
|
||||||
removeBubble: null
|
showLayer: LayerEvent;
|
||||||
onPlayerMove: undefined
|
hideLayer: LayerEvent;
|
||||||
showLayer: LayerEvent
|
setProperty: SetPropertyEvent;
|
||||||
hideLayer: LayerEvent
|
getDataLayer: undefined;
|
||||||
setProperty: SetPropertyEvent
|
loadSound: LoadSoundEvent;
|
||||||
getDataLayer: undefined
|
playSound: PlaySoundEvent;
|
||||||
loadSound: LoadSoundEvent
|
stopSound: null;
|
||||||
playSound: PlaySoundEvent
|
getState: undefined;
|
||||||
stopSound: null,
|
registerMenuCommand: MenuItemRegisterEvent;
|
||||||
getState: undefined,
|
setTiles: SetTilesEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent
|
};
|
||||||
}
|
|
||||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||||
type: T;
|
type: T;
|
||||||
data: IframeEventMap[T];
|
data: IframeEventMap[T];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> => typeof event.type === 'string';
|
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> =>
|
||||||
|
typeof event.type === "string";
|
||||||
|
|
||||||
export interface IframeResponseEventMap {
|
export interface IframeResponseEventMap {
|
||||||
userInputChat: UserInputChatEvent
|
userInputChat: UserInputChatEvent;
|
||||||
enterEvent: EnterLeaveEvent
|
enterEvent: EnterLeaveEvent;
|
||||||
leaveEvent: EnterLeaveEvent
|
leaveEvent: EnterLeaveEvent;
|
||||||
buttonClickedEvent: ButtonClickedEvent
|
buttonClickedEvent: ButtonClickedEvent;
|
||||||
gameState: GameStateEvent
|
hasPlayerMoved: HasPlayerMovedEvent;
|
||||||
hasPlayerMoved: HasPlayerMovedEvent
|
dataLayer: DataLayerEvent;
|
||||||
dataLayer: DataLayerEvent
|
menuItemClicked: MenuItemClickedEvent;
|
||||||
menuItemClicked: MenuItemClickedEvent
|
|
||||||
}
|
}
|
||||||
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
||||||
type: T;
|
type: T;
|
||||||
@ -75,4 +75,49 @@ export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isIframeResponseEventWrapper = (event: { type?: string }): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === 'string';
|
export const isIframeResponseEventWrapper = (event: {
|
||||||
|
type?: string;
|
||||||
|
}): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === "string";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame
|
||||||
|
*/
|
||||||
|
export type IframeQueryMap = {
|
||||||
|
getState: {
|
||||||
|
query: undefined,
|
||||||
|
answer: GameStateEvent
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IframeQuery<T extends keyof IframeQueryMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeQueryMap[T]['query'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IframeQueryWrapper<T extends keyof IframeQueryMap> {
|
||||||
|
id: number;
|
||||||
|
query: IframeQuery<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQueryMap> => typeof event.type === 'string';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper<keyof IframeQueryMap> => typeof event.id === 'number' && isIframeQuery(event.query);
|
||||||
|
|
||||||
|
export interface IframeAnswerEvent<T extends keyof IframeQueryMap> {
|
||||||
|
id: number;
|
||||||
|
type: T;
|
||||||
|
data: IframeQueryMap[T]['answer'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIframeAnswerEvent = (event: { type?: string, id?: number }): event is IframeAnswerEvent<keyof IframeQueryMap> => typeof event.type === 'string' && typeof event.id === 'number';
|
||||||
|
|
||||||
|
export interface IframeErrorAnswerEvent {
|
||||||
|
id: number;
|
||||||
|
type: keyof IframeQueryMap;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIframeErrorAnswerEvent = (event: { type?: string, id?: number, error?: string }): event is IframeErrorAnswerEvent => typeof event.type === 'string' && typeof event.id === 'number' && typeof event.error === 'string';
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isLayerEvent =
|
export const isLayerEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
name: tg.isString,
|
name: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to show/hide a layer.
|
* A message sent from the iFrame to the game to show/hide a layer.
|
||||||
*/
|
*/
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isLoadPageEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isLoadPageEvent =
|
|
||||||
new tg.IsInterface().withProperties({
|
|
||||||
url: tg.isString,
|
url: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to add a message in the chat.
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
*/
|
*/
|
||||||
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>;
|
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isOpenCoWebsite = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
export const isOpenCoWebsite =
|
|
||||||
new tg.IsInterface().withProperties({
|
|
||||||
url: tg.isString,
|
url: tg.isString,
|
||||||
}).get();
|
allowApi: tg.isBoolean,
|
||||||
|
allowPolicy: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to add a message in the chat.
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
16
front/src/Api/Events/SetTilesEvent.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isSetTilesEvent = tg.isArray(
|
||||||
|
new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
tile: tg.isUnion(tg.isUnion(tg.isNumber, tg.isString), tg.isNull),
|
||||||
|
layer: tg.isString,
|
||||||
|
})
|
||||||
|
.get()
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to set one or many tiles.
|
||||||
|
*/
|
||||||
|
export type SetTilesEvent = tg.GuardedType<typeof isSetTilesEvent>;
|
@ -1,12 +1,13 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isSetPropertyEvent =
|
export const isSetPropertyEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
layerName: tg.isString,
|
layerName: tg.isString,
|
||||||
propertyName: tg.isString,
|
propertyName: tg.isString,
|
||||||
propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined)))
|
propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined))),
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to change the value of the property of the layer
|
* A message sent from the iFrame to the game to change the value of the property of the layer
|
||||||
*/
|
*/
|
||||||
export type SetPropertyEvent = tg.GuardedType<typeof isSetPropertyEvent>;
|
export type SetPropertyEvent = tg.GuardedType<typeof isSetPropertyEvent>;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
export const isMenuItemClickedEvent =
|
export const isMenuItemClickedEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
menuItem: tg.isString
|
menuItem: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the game to the iFrame when a menu item is clicked.
|
* A message sent from the game to the iFrame when a menu item is clicked.
|
||||||
*/
|
*/
|
||||||
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>;
|
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
export const isMenuItemRegisterEvent =
|
export const isMenuItemRegisterEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
menutItem: tg.isString
|
menutItem: tg.isString,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
/**
|
/**
|
||||||
* A message sent from the iFrame to the game to add a new menu item.
|
* A message sent from the iFrame to the game to add a new menu item.
|
||||||
*/
|
*/
|
||||||
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
||||||
|
|
||||||
export const isMenuItemRegisterIframeEvent =
|
export const isMenuItemRegisterIframeEvent = new tg.IsInterface()
|
||||||
new tg.IsInterface().withProperties({
|
.withProperties({
|
||||||
type: tg.isSingletonString("registerMenuCommand"),
|
type: tg.isSingletonString("registerMenuCommand"),
|
||||||
data: isMenuItemRegisterEvent
|
data: isMenuItemRegisterEvent,
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
const _registerMenuCommandStream: Subject<string> = new Subject();
|
const _registerMenuCommandStream: Subject<string> = new Subject();
|
||||||
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
||||||
|
|
||||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
||||||
_registerMenuCommandStream.next(event.menutItem)
|
_registerMenuCommandStream.next(event.menutItem);
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,45 @@
|
|||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||||
import type {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||||
import type {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||||
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||||
import {scriptUtils} from "./ScriptUtils";
|
import { scriptUtils } from "./ScriptUtils";
|
||||||
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||||
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||||
import {
|
import {
|
||||||
|
IframeErrorAnswerEvent,
|
||||||
IframeEvent,
|
IframeEvent,
|
||||||
IframeEventMap,
|
IframeEventMap, IframeQueryMap,
|
||||||
IframeResponseEvent,
|
IframeResponseEvent,
|
||||||
IframeResponseEventMap,
|
IframeResponseEventMap,
|
||||||
isIframeEventWrapper,
|
isIframeEventWrapper,
|
||||||
TypedMessageEvent
|
isIframeQueryWrapper,
|
||||||
|
TypedMessageEvent,
|
||||||
} from "./Events/IframeEvent";
|
} from "./Events/IframeEvent";
|
||||||
import type {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||||
//import { isLoadPageEvent } from './Events/LoadPageEvent';
|
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
|
||||||
import {isPlaySoundEvent, PlaySoundEvent} from "./Events/PlaySoundEvent";
|
import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
||||||
import {isStopSoundEvent, StopSoundEvent} from "./Events/StopSoundEvent";
|
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
||||||
import {isLoadSoundEvent, LoadSoundEvent} from "./Events/LoadSoundEvent";
|
import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
|
||||||
import {isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
|
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||||
import {isLayerEvent, LayerEvent} from "./Events/LayerEvent";
|
import { isMenuItemRegisterEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
import {isMenuItemRegisterEvent,} from "./Events/ui/MenuItemRegisterEvent";
|
import type { DataLayerEvent } from "./Events/DataLayerEvent";
|
||||||
import type {DataLayerEvent} from "./Events/DataLayerEvent";
|
import type { GameStateEvent } from "./Events/GameStateEvent";
|
||||||
import type {GameStateEvent} from "./Events/GameStateEvent";
|
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||||
import type {HasPlayerMovedEvent} from "./Events/HasPlayerMovedEvent";
|
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||||
import {isLoadPageEvent} from "./Events/LoadPageEvent";
|
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
import {handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent} from "./Events/ui/MenuItemRegisterEvent";
|
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||||
|
|
||||||
|
type AnswererCallback<T extends keyof IframeQueryMap> = (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise<IframeQueryMap[T]['answer']>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
* Also allows to send messages to those iframes.
|
* Also allows to send messages to those iframes.
|
||||||
*/
|
*/
|
||||||
class IframeListener {
|
class IframeListener {
|
||||||
|
|
||||||
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||||
public readonly chatStream = this._chatStream.asObservable();
|
public readonly chatStream = this._chatStream.asObservable();
|
||||||
|
|
||||||
@ -82,9 +85,6 @@ class IframeListener {
|
|||||||
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
||||||
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
||||||
|
|
||||||
private readonly _gameStateStream: Subject<void> = new Subject();
|
|
||||||
public readonly gameStateStream = this._gameStateStream.asObservable();
|
|
||||||
|
|
||||||
private readonly _dataLayerChangeStream: Subject<void> = new Subject();
|
private readonly _dataLayerChangeStream: Subject<void> = new Subject();
|
||||||
public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable();
|
public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable();
|
||||||
|
|
||||||
@ -103,111 +103,155 @@ class IframeListener {
|
|||||||
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
||||||
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
||||||
|
public readonly setTilesStream = this._setTilesStream.asObservable();
|
||||||
|
|
||||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
||||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
private sendPlayerMove: boolean = false;
|
private sendPlayerMove: boolean = false;
|
||||||
|
|
||||||
|
private answerers: {
|
||||||
|
[key in keyof IframeQueryMap]?: AnswererCallback<key>
|
||||||
|
} = {};
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
|
window.addEventListener(
|
||||||
// Do we trust the sender of this message?
|
"message",
|
||||||
// Let's only accept messages from the iframe that are allowed.
|
(message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
|
||||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
// Do we trust the sender of this message?
|
||||||
let foundSrc: string | undefined;
|
// Let's only accept messages from the iframe that are allowed.
|
||||||
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
|
let foundSrc: string | undefined;
|
||||||
|
|
||||||
let iframe: HTMLIFrameElement;
|
let iframe: HTMLIFrameElement | undefined;
|
||||||
for (iframe of this.iframes) {
|
for (iframe of this.iframes) {
|
||||||
if (iframe.contentWindow === message.source) {
|
if (iframe.contentWindow === message.source) {
|
||||||
foundSrc = iframe.src;
|
foundSrc = iframe.src;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (foundSrc === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = message.data;
|
|
||||||
if (isIframeEventWrapper(payload)) {
|
|
||||||
if (payload.type === 'showLayer' && isLayerEvent(payload.data)) {
|
|
||||||
this._showLayerStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'hideLayer' && isLayerEvent(payload.data)) {
|
|
||||||
this._hideLayerStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'setProperty' && isSetPropertyEvent(payload.data)) {
|
|
||||||
this._setPropertyStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'chat' && isChatEvent(payload.data)) {
|
|
||||||
this._chatStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'openPopup' && isOpenPopupEvent(payload.data)) {
|
|
||||||
this._openPopupStream.next(payload.data);
|
|
||||||
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
|
||||||
this._closePopupStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
|
||||||
scriptUtils.openTab(payload.data.url);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
|
||||||
scriptUtils.goToPage(payload.data.url);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'loadPage' && isLoadPageEvent(payload.data)) {
|
|
||||||
this._loadPageStream.next(payload.data.url);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
|
||||||
this._playSoundStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'stopSound' && isStopSoundEvent(payload.data)) {
|
|
||||||
this._stopSoundStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) {
|
|
||||||
this._loadSoundStream.next(payload.data);
|
|
||||||
}
|
|
||||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
|
||||||
scriptUtils.openCoWebsite(payload.data.url, foundSrc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (payload.type === 'closeCoWebSite') {
|
const payload = message.data;
|
||||||
scriptUtils.closeCoWebSite();
|
|
||||||
|
if (foundSrc === undefined || iframe === undefined) {
|
||||||
|
if (isIframeEventWrapper(payload)) {
|
||||||
|
console.warn(
|
||||||
|
"It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " +
|
||||||
|
"If you are looking to use the WorkAdventure Scripting API inside an iFrame, you should allow the " +
|
||||||
|
'iFrame to communicate with WorkAdventure by using the "openWebsiteAllowApi" property in your map (or passing "true" as a second' +
|
||||||
|
"parameter to WA.nav.openCoWebSite())"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (payload.type === 'disablePlayerControls') {
|
foundSrc = this.getBaseUrl(foundSrc, message.source);
|
||||||
this._disablePlayerControlStream.next();
|
|
||||||
}
|
|
||||||
else if (payload.type === 'restorePlayerControls') {
|
|
||||||
this._enablePlayerControlStream.next();
|
|
||||||
} else if (payload.type === 'displayBubble') {
|
|
||||||
this._displayBubbleStream.next();
|
|
||||||
} else if (payload.type === 'removeBubble') {
|
|
||||||
this._removeBubbleStream.next();
|
|
||||||
} else if (payload.type == "getState") {
|
|
||||||
this._gameStateStream.next();
|
|
||||||
} else if (payload.type == "onPlayerMove") {
|
|
||||||
this.sendPlayerMove = true
|
|
||||||
} else if (payload.type == "getDataLayer") {
|
|
||||||
this._dataLayerChangeStream.next();
|
|
||||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
|
||||||
const data = payload.data.menutItem;
|
|
||||||
// @ts-ignore
|
|
||||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
|
||||||
this._unregisterMenuCommandStream.next(data);
|
|
||||||
})
|
|
||||||
handleMenuItemRegistrationEvent(payload.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
|
if (isIframeQueryWrapper(payload)) {
|
||||||
|
const queryId = payload.id;
|
||||||
|
const query = payload.query;
|
||||||
|
|
||||||
|
const answerer = this.answerers[query.type];
|
||||||
|
if (answerer === undefined) {
|
||||||
|
const errorMsg = 'The iFrame sent a message of type "'+query.type+'" but there is no service configured to answer these messages.';
|
||||||
|
console.error(errorMsg);
|
||||||
|
iframe.contentWindow?.postMessage({
|
||||||
|
id: queryId,
|
||||||
|
type: query.type,
|
||||||
|
error: errorMsg
|
||||||
|
} as IframeErrorAnswerEvent, '*');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.resolve(answerer(query.data)).then((value) => {
|
||||||
|
iframe?.contentWindow?.postMessage({
|
||||||
|
id: queryId,
|
||||||
|
type: query.type,
|
||||||
|
data: value
|
||||||
|
}, '*');
|
||||||
|
}).catch(reason => {
|
||||||
|
console.error('An error occurred while responding to an iFrame query.', reason);
|
||||||
|
let reasonMsg: string;
|
||||||
|
if (reason instanceof Error) {
|
||||||
|
reasonMsg = reason.message;
|
||||||
|
} else {
|
||||||
|
reasonMsg = reason.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe?.contentWindow?.postMessage({
|
||||||
|
id: queryId,
|
||||||
|
type: query.type,
|
||||||
|
error: reasonMsg
|
||||||
|
} as IframeErrorAnswerEvent, '*');
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (isIframeEventWrapper(payload)) {
|
||||||
|
if (payload.type === "showLayer" && isLayerEvent(payload.data)) {
|
||||||
|
this._showLayerStream.next(payload.data);
|
||||||
|
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) {
|
||||||
|
this._hideLayerStream.next(payload.data);
|
||||||
|
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||||
|
this._setPropertyStream.next(payload.data);
|
||||||
|
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||||
|
this._chatStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||||
|
this._openPopupStream.next(payload.data);
|
||||||
|
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
|
||||||
|
this._closePopupStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) {
|
||||||
|
scriptUtils.openTab(payload.data.url);
|
||||||
|
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) {
|
||||||
|
scriptUtils.goToPage(payload.data.url);
|
||||||
|
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) {
|
||||||
|
this._loadPageStream.next(payload.data.url);
|
||||||
|
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) {
|
||||||
|
this._playSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) {
|
||||||
|
this._stopSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
|
||||||
|
this._loadSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
|
||||||
|
scriptUtils.openCoWebsite(
|
||||||
|
payload.data.url,
|
||||||
|
foundSrc,
|
||||||
|
payload.data.allowApi,
|
||||||
|
payload.data.allowPolicy
|
||||||
|
);
|
||||||
|
} else if (payload.type === "closeCoWebSite") {
|
||||||
|
scriptUtils.closeCoWebSite();
|
||||||
|
} else if (payload.type === "disablePlayerControls") {
|
||||||
|
this._disablePlayerControlStream.next();
|
||||||
|
} else if (payload.type === "restorePlayerControls") {
|
||||||
|
this._enablePlayerControlStream.next();
|
||||||
|
} else if (payload.type === "displayBubble") {
|
||||||
|
this._displayBubbleStream.next();
|
||||||
|
} else if (payload.type === "removeBubble") {
|
||||||
|
this._removeBubbleStream.next();
|
||||||
|
} else if (payload.type == "onPlayerMove") {
|
||||||
|
this.sendPlayerMove = true;
|
||||||
|
} else if (payload.type == "getDataLayer") {
|
||||||
|
this._dataLayerChangeStream.next();
|
||||||
|
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||||
|
const data = payload.data.menutItem;
|
||||||
|
// @ts-ignore
|
||||||
|
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||||
|
this._unregisterMenuCommandStream.next(data);
|
||||||
|
});
|
||||||
|
handleMenuItemRegistrationEvent(payload.data);
|
||||||
|
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||||
|
this._setTilesStream.next(payload.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDataLayerEvent(dataLayerEvent: DataLayerEvent) {
|
sendDataLayerEvent(dataLayerEvent: DataLayerEvent) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type' : 'dataLayer',
|
type: "dataLayer",
|
||||||
'data' : dataLayerEvent
|
data: dataLayerEvent,
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sendGameStateEvent(gameStateEvent: GameStateEvent) {
|
|
||||||
this.postMessage({
|
|
||||||
'type': 'gameState',
|
|
||||||
'data': gameStateEvent
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,25 +264,25 @@ class IframeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unregisterIframe(iframe: HTMLIFrameElement): void {
|
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||||
this.iframeCloseCallbacks.get(iframe)?.forEach(callback => {
|
this.iframeCloseCallbacks.get(iframe)?.forEach((callback) => {
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
this.iframes.delete(iframe);
|
this.iframes.delete(iframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerScript(scriptUrl: string): void {
|
registerScript(scriptUrl: string): void {
|
||||||
console.log('Loading map related script at ', scriptUrl)
|
console.log("Loading map related script at ", scriptUrl);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||||
// Using external iframe mode (
|
// Using external iframe mode (
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement("iframe");
|
||||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = "none";
|
||||||
iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl);
|
iframe.src = "/iframe.html?script=" + encodeURIComponent(scriptUrl);
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
iframe.sandbox.add('allow-scripts');
|
iframe.sandbox.add("allow-scripts");
|
||||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||||
|
|
||||||
document.body.prepend(iframe);
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
@ -246,36 +290,50 @@ class IframeListener {
|
|||||||
this.registerIframe(iframe);
|
this.registerIframe(iframe);
|
||||||
} else {
|
} else {
|
||||||
// production code
|
// production code
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement("iframe");
|
||||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = "none";
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
iframe.sandbox.add('allow-scripts');
|
iframe.sandbox.add("allow-scripts");
|
||||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||||
|
|
||||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||||
iframe.srcdoc = '<!doctype html>\n' +
|
iframe.srcdoc =
|
||||||
'\n' +
|
"<!doctype html>\n" +
|
||||||
|
"\n" +
|
||||||
'<html lang="en">\n' +
|
'<html lang="en">\n' +
|
||||||
'<head>\n' +
|
"<head>\n" +
|
||||||
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
'<script src="' +
|
||||||
'<script src="' + scriptUrl + '" ></script>\n' +
|
window.location.protocol +
|
||||||
'<title></title>\n' +
|
"//" +
|
||||||
'</head>\n' +
|
window.location.host +
|
||||||
'</html>\n';
|
'/iframe_api.js" ></script>\n' +
|
||||||
|
'<script src="' +
|
||||||
|
scriptUrl +
|
||||||
|
'" ></script>\n' +
|
||||||
|
"<title></title>\n" +
|
||||||
|
"</head>\n" +
|
||||||
|
"</html>\n";
|
||||||
|
|
||||||
document.body.prepend(iframe);
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
this.scripts.set(scriptUrl, iframe);
|
this.scripts.set(scriptUrl, iframe);
|
||||||
this.registerIframe(iframe);
|
this.registerIframe(iframe);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBaseUrl(src: string, source: MessageEventSource | null): string {
|
||||||
|
for (const script of this.scripts) {
|
||||||
|
if (script[1].contentWindow === source) {
|
||||||
|
return script[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getIFrameId(scriptUrl: string): string {
|
private static getIFrameId(scriptUrl: string): string {
|
||||||
return 'script' + btoa(scriptUrl);
|
return "script" + btoa(scriptUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterScript(scriptUrl: string): void {
|
unregisterScript(scriptUrl: string): void {
|
||||||
@ -292,47 +350,47 @@ class IframeListener {
|
|||||||
|
|
||||||
sendUserInputChat(message: string) {
|
sendUserInputChat(message: string) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'userInputChat',
|
type: "userInputChat",
|
||||||
'data': {
|
data: {
|
||||||
'message': message,
|
message: message,
|
||||||
} as UserInputChatEvent
|
} as UserInputChatEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEnterEvent(name: string) {
|
sendEnterEvent(name: string) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'enterEvent',
|
type: "enterEvent",
|
||||||
'data': {
|
data: {
|
||||||
"name": name
|
name: name,
|
||||||
} as EnterLeaveEvent
|
} as EnterLeaveEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendLeaveEvent(name: string) {
|
sendLeaveEvent(name: string) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'leaveEvent',
|
type: "leaveEvent",
|
||||||
'data': {
|
data: {
|
||||||
"name": name
|
name: name,
|
||||||
} as EnterLeaveEvent
|
} as EnterLeaveEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPlayerMoved(event: HasPlayerMovedEvent) {
|
hasPlayerMoved(event: HasPlayerMovedEvent) {
|
||||||
if (this.sendPlayerMove) {
|
if (this.sendPlayerMove) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'hasPlayerMoved',
|
type: "hasPlayerMoved",
|
||||||
'data': event
|
data: event,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'buttonClickedEvent',
|
type: "buttonClickedEvent",
|
||||||
'data': {
|
data: {
|
||||||
popupId,
|
popupId,
|
||||||
buttonId
|
buttonId,
|
||||||
} as ButtonClickedEvent
|
} as ButtonClickedEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,10 +399,25 @@ class IframeListener {
|
|||||||
*/
|
*/
|
||||||
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
iframe.contentWindow?.postMessage(message, '*');
|
iframe.contentWindow?.postMessage(message, "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback that can be used to respond to some query (as defined in the IframeQueryMap type).
|
||||||
|
*
|
||||||
|
* Important! There can be only one "answerer" so registering a new one will unregister the old one.
|
||||||
|
*
|
||||||
|
* @param key The "type" of the query we are answering
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public registerAnswerer<T extends keyof IframeQueryMap>(key: T, callback: (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise<IframeQueryMap[T]['answer']> ): void {
|
||||||
|
this.answerers[key] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregisterAnswerer(key: keyof IframeQueryMap): void {
|
||||||
|
delete this.answerers[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const iframeListener = new IframeListener();
|
export const iframeListener = new IframeListener();
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import {coWebsiteManager} from "../WebRtc/CoWebsiteManager";
|
import { coWebsiteManager } from "../WebRtc/CoWebsiteManager";
|
||||||
|
|
||||||
class ScriptUtils {
|
class ScriptUtils {
|
||||||
|
public openTab(url: string) {
|
||||||
public openTab(url : string){
|
|
||||||
window.open(url);
|
window.open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public goToPage(url : string){
|
public goToPage(url: string) {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCoWebsite(url: string, base: string) {
|
public openCoWebsite(url: string, base: string, api: boolean, policy: string) {
|
||||||
coWebsiteManager.loadCoWebsite(url, base);
|
coWebsiteManager.loadCoWebsite(url, base, api, policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeCoWebSite(){
|
public closeCoWebSite() {
|
||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,40 @@
|
|||||||
import type * as tg from "generic-type-guard";
|
import type * as tg from "generic-type-guard";
|
||||||
import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent';
|
import type {
|
||||||
|
IframeEvent,
|
||||||
|
IframeEventMap, IframeQuery,
|
||||||
|
IframeQueryMap,
|
||||||
|
IframeResponseEventMap
|
||||||
|
} from '../Events/IframeEvent';
|
||||||
|
import type {IframeQueryWrapper} from "../Events/IframeEvent";
|
||||||
|
|
||||||
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
||||||
window.parent.postMessage(content, "*")
|
window.parent.postMessage(content, "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let queryNumber = 0;
|
||||||
|
|
||||||
|
export const answerPromises = new Map<number, {
|
||||||
|
resolve: (value: (IframeQueryMap[keyof IframeQueryMap]['answer'] | PromiseLike<IframeQueryMap[keyof IframeQueryMap]['answer']>)) => void,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
reject: (reason?: any) => void
|
||||||
|
}>();
|
||||||
|
|
||||||
|
export function queryWorkadventure<T extends keyof IframeQueryMap>(content: IframeQuery<T>): Promise<IframeQueryMap[T]['answer']> {
|
||||||
|
return new Promise<IframeQueryMap[T]['answer']>((resolve, reject) => {
|
||||||
|
window.parent.postMessage({
|
||||||
|
id: queryNumber,
|
||||||
|
query: content
|
||||||
|
} as IframeQueryWrapper<T>, "*");
|
||||||
|
|
||||||
|
answerPromises.set(queryNumber, {
|
||||||
|
resolve,
|
||||||
|
reject
|
||||||
|
});
|
||||||
|
|
||||||
|
queryNumber++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
||||||
|
|
||||||
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import type { MenuItemClickedEvent } from '../../Events/ui/MenuItemClickedEvent';
|
import type { MenuItemClickedEvent } from "../../Events/ui/MenuItemClickedEvent";
|
||||||
import { iframeListener } from '../../IframeListener';
|
import { iframeListener } from "../../IframeListener";
|
||||||
|
|
||||||
export function sendMenuClickedEvent(menuItem: string) {
|
export function sendMenuClickedEvent(menuItem: string) {
|
||||||
iframeListener.postMessage({
|
iframeListener.postMessage({
|
||||||
'type': 'menuItemClicked',
|
type: "menuItemClicked",
|
||||||
'data': {
|
data: {
|
||||||
menuItem: menuItem,
|
menuItem: menuItem,
|
||||||
} as MenuItemClickedEvent
|
} as MenuItemClickedEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
import type { ChatEvent } from '../Events/ChatEvent'
|
import type { ChatEvent } from "../Events/ChatEvent";
|
||||||
import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent'
|
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
const chatStream = new Subject<string>();
|
const chatStream = new Subject<string>();
|
||||||
|
|
||||||
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
|
export class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
|
||||||
|
callbacks = [
|
||||||
callbacks = [apiCallback({
|
apiCallback({
|
||||||
callback: (event: UserInputChatEvent) => {
|
callback: (event: UserInputChatEvent) => {
|
||||||
chatStream.next(event.message);
|
chatStream.next(event.message);
|
||||||
},
|
},
|
||||||
type: "userInputChat",
|
type: "userInputChat",
|
||||||
typeChecker: isUserInputChatEvent
|
typeChecker: isUserInputChatEvent,
|
||||||
})]
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
sendChatMessage(message: string, author: string) {
|
sendChatMessage(message: string, author: string) {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: 'chat',
|
type: "chat",
|
||||||
data: {
|
data: {
|
||||||
'message': message,
|
message: message,
|
||||||
'author': author
|
author: author,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,4 +35,4 @@ class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventureChatCommands()
|
export default new WorkadventureChatCommands();
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
|
|
||||||
class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
|
export class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
|
||||||
callbacks = []
|
callbacks = [];
|
||||||
|
|
||||||
disablePlayerControls(): void {
|
disablePlayerControls(): void {
|
||||||
sendToWorkadventure({ 'type': 'disablePlayerControls', data: null });
|
sendToWorkadventure({ type: "disablePlayerControls", data: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
restorePlayerControls(): void {
|
restorePlayerControls(): void {
|
||||||
sendToWorkadventure({ 'type': 'restorePlayerControls', data: null });
|
sendToWorkadventure({ type: "restorePlayerControls", data: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureControlsCommands();
|
export default new WorkadventureControlsCommands();
|
||||||
|
@ -1,57 +1,56 @@
|
|||||||
import type { GoToPageEvent } from '../Events/GoToPageEvent';
|
import type { GoToPageEvent } from "../Events/GoToPageEvent";
|
||||||
import type { OpenTabEvent } from '../Events/OpenTabEvent';
|
import type { OpenTabEvent } from "../Events/OpenTabEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent";
|
import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent";
|
||||||
import type {LoadPageEvent} from "../Events/LoadPageEvent";
|
import type { LoadPageEvent } from "../Events/LoadPageEvent";
|
||||||
|
|
||||||
|
|
||||||
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
|
|
||||||
callbacks = []
|
|
||||||
|
|
||||||
|
export class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
|
||||||
|
callbacks = [];
|
||||||
|
|
||||||
openTab(url: string): void {
|
openTab(url: string): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'openTab',
|
type: "openTab",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goToPage(url: string): void {
|
goToPage(url: string): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'goToPage',
|
type: "goToPage",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goToRoom(url: string): void {
|
goToRoom(url: string): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'loadPage',
|
type: "loadPage",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openCoWebSite(url: string): void {
|
openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'openCoWebSite',
|
type: "openCoWebSite",
|
||||||
"data": {
|
data: {
|
||||||
url
|
url,
|
||||||
}
|
allowApi,
|
||||||
|
allowPolicy,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
closeCoWebSite(): void {
|
closeCoWebSite(): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'closeCoWebSite',
|
type: "closeCoWebSite",
|
||||||
data: null
|
data: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureNavigationCommands();
|
export default new WorkadventureNavigationCommands();
|
||||||
|
@ -1,29 +1,41 @@
|
|||||||
import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent";
|
||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import {apiCallback} from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent";
|
import { getGameState } from "./room";
|
||||||
|
import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent";
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: string | undefined;
|
||||||
|
nickName: string | null;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
const moveStream = new Subject<HasPlayerMovedEvent>();
|
const moveStream = new Subject<HasPlayerMovedEvent>();
|
||||||
|
|
||||||
class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
||||||
callbacks = [
|
callbacks = [
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: 'hasPlayerMoved',
|
type: "hasPlayerMoved",
|
||||||
typeChecker: isHasPlayerMovedEvent,
|
typeChecker: isHasPlayerMovedEvent,
|
||||||
callback: (payloadData) => {
|
callback: (payloadData) => {
|
||||||
moveStream.next(payloadData);
|
moveStream.next(payloadData);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
]
|
];
|
||||||
|
|
||||||
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
|
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
|
||||||
moveStream.subscribe(callback);
|
moveStream.subscribe(callback);
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: 'onPlayerMove',
|
type: "onPlayerMove",
|
||||||
data: null
|
data: null,
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
getCurrentUser(): Promise<User> {
|
||||||
|
return getGameState().then((gameState) => {
|
||||||
|
return { id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventurePlayerCommands();
|
export default new WorkadventurePlayerCommands();
|
||||||
|
@ -1,87 +1,74 @@
|
|||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import { EnterLeaveEvent, isEnterLeaveEvent } from '../Events/EnterLeaveEvent';
|
|
||||||
import {IframeApiContribution, sendToWorkadventure} from './IframeApiContribution';
|
import { isDataLayerEvent } from "../Events/DataLayerEvent";
|
||||||
|
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
||||||
|
import { isGameStateEvent } from "../Events/GameStateEvent";
|
||||||
|
|
||||||
|
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import type {LayerEvent} from "../Events/LayerEvent";
|
|
||||||
import type {SetPropertyEvent} from "../Events/setPropertyEvent";
|
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||||
import type {GameStateEvent} from "../Events/GameStateEvent";
|
import type { DataLayerEvent } from "../Events/DataLayerEvent";
|
||||||
import type {ITiledMap} from "../../Phaser/Map/ITiledMap";
|
import type { GameStateEvent } from "../Events/GameStateEvent";
|
||||||
import type {DataLayerEvent} from "../Events/DataLayerEvent";
|
|
||||||
import {isGameStateEvent} from "../Events/GameStateEvent";
|
|
||||||
import {isDataLayerEvent} from "../Events/DataLayerEvent";
|
|
||||||
|
|
||||||
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
const dataLayerResolver = new Subject<DataLayerEvent>();
|
const dataLayerResolver = new Subject<DataLayerEvent>();
|
||||||
const stateResolvers = new Subject<GameStateEvent>();
|
|
||||||
|
|
||||||
let immutableData: GameStateEvent;
|
let immutableDataPromise: Promise<GameStateEvent> | undefined = undefined;
|
||||||
|
|
||||||
interface Room {
|
interface Room {
|
||||||
id: string,
|
id: string;
|
||||||
mapUrl: string,
|
mapUrl: string;
|
||||||
map: ITiledMap,
|
map: ITiledMap;
|
||||||
startLayer: string | null
|
startLayer: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface TileDescriptor {
|
||||||
id: string | undefined,
|
x: number;
|
||||||
nickName: string | null,
|
y: number;
|
||||||
tags: string[]
|
tile: number | string | null;
|
||||||
|
layer: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGameState(): Promise<GameStateEvent> {
|
||||||
function getGameState(): Promise<GameStateEvent> {
|
if (immutableDataPromise === undefined) {
|
||||||
if (immutableData) {
|
immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined });
|
||||||
return Promise.resolve(immutableData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new Promise<GameStateEvent>((resolver, thrower) => {
|
|
||||||
stateResolvers.subscribe(resolver);
|
|
||||||
sendToWorkadventure({type: "getState", data: null});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return immutableDataPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataLayer(): Promise<DataLayerEvent> {
|
function getDataLayer(): Promise<DataLayerEvent> {
|
||||||
return new Promise<DataLayerEvent>((resolver, thrower) => {
|
return new Promise<DataLayerEvent>((resolver, thrower) => {
|
||||||
dataLayerResolver.subscribe(resolver);
|
dataLayerResolver.subscribe(resolver);
|
||||||
sendToWorkadventure({type: "getDataLayer", data: null})
|
sendToWorkadventure({ type: "getDataLayer", data: null });
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
export class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
||||||
callbacks = [
|
callbacks = [
|
||||||
apiCallback({
|
apiCallback({
|
||||||
callback: (payloadData: EnterLeaveEvent) => {
|
callback: (payloadData: EnterLeaveEvent) => {
|
||||||
enterStreams.get(payloadData.name)?.next();
|
enterStreams.get(payloadData.name)?.next();
|
||||||
},
|
},
|
||||||
type: "enterEvent",
|
type: "enterEvent",
|
||||||
typeChecker: isEnterLeaveEvent
|
typeChecker: isEnterLeaveEvent,
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "leaveEvent",
|
type: "leaveEvent",
|
||||||
typeChecker: isEnterLeaveEvent,
|
typeChecker: isEnterLeaveEvent,
|
||||||
callback: (payloadData) => {
|
callback: (payloadData) => {
|
||||||
leaveStreams.get(payloadData.name)?.next();
|
leaveStreams.get(payloadData.name)?.next();
|
||||||
}
|
},
|
||||||
}),
|
|
||||||
apiCallback({
|
|
||||||
type: "gameState",
|
|
||||||
typeChecker: isGameStateEvent,
|
|
||||||
callback: (payloadData) => {
|
|
||||||
stateResolvers.next(payloadData);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "dataLayer",
|
type: "dataLayer",
|
||||||
typeChecker: isDataLayerEvent,
|
typeChecker: isDataLayerEvent,
|
||||||
callback: (payloadData) => {
|
callback: (payloadData) => {
|
||||||
dataLayerResolver.next(payloadData);
|
dataLayerResolver.next(payloadData);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
onEnterZone(name: string, callback: () => void): void {
|
onEnterZone(name: string, callback: () => void): void {
|
||||||
let subject = enterStreams.get(name);
|
let subject = enterStreams.get(name);
|
||||||
@ -90,7 +77,6 @@ class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomC
|
|||||||
enterStreams.set(name, subject);
|
enterStreams.set(name, subject);
|
||||||
}
|
}
|
||||||
subject.subscribe(callback);
|
subject.subscribe(callback);
|
||||||
|
|
||||||
}
|
}
|
||||||
onLeaveZone(name: string, callback: () => void): void {
|
onLeaveZone(name: string, callback: () => void): void {
|
||||||
let subject = leaveStreams.get(name);
|
let subject = leaveStreams.get(name);
|
||||||
@ -101,35 +87,39 @@ class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomC
|
|||||||
subject.subscribe(callback);
|
subject.subscribe(callback);
|
||||||
}
|
}
|
||||||
showLayer(layerName: string): void {
|
showLayer(layerName: string): void {
|
||||||
sendToWorkadventure({type: 'showLayer', data: {'name': layerName}});
|
sendToWorkadventure({ type: "showLayer", data: { name: layerName } });
|
||||||
}
|
}
|
||||||
hideLayer(layerName: string): void {
|
hideLayer(layerName: string): void {
|
||||||
sendToWorkadventure({type: 'hideLayer', data: {'name': layerName}});
|
sendToWorkadventure({ type: "hideLayer", data: { name: layerName } });
|
||||||
}
|
}
|
||||||
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: 'setProperty',
|
type: "setProperty",
|
||||||
data: {
|
data: {
|
||||||
'layerName': layerName,
|
layerName: layerName,
|
||||||
'propertyName': propertyName,
|
propertyName: propertyName,
|
||||||
'propertyValue': propertyValue,
|
propertyValue: propertyValue,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
getCurrentRoom(): Promise<Room> {
|
getCurrentRoom(): Promise<Room> {
|
||||||
return getGameState().then((gameState) => {
|
return getGameState().then((gameState) => {
|
||||||
return getDataLayer().then((mapJson) => {
|
return getDataLayer().then((mapJson) => {
|
||||||
return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName};
|
return {
|
||||||
})
|
id: gameState.roomId,
|
||||||
})
|
map: mapJson.data as ITiledMap,
|
||||||
|
mapUrl: gameState.mapUrl,
|
||||||
|
startLayer: gameState.startLayerName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
getCurrentUser(): Promise<User> {
|
setTiles(tiles: TileDescriptor[]) {
|
||||||
return getGameState().then((gameState) => {
|
sendToWorkadventure({
|
||||||
return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags};
|
type: "setTiles",
|
||||||
})
|
data: tiles,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureRoomCommands();
|
export default new WorkadventureRoomCommands();
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import type { LoadSoundEvent } from '../Events/LoadSoundEvent';
|
import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
|
||||||
import type { PlaySoundEvent } from '../Events/PlaySoundEvent';
|
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
|
||||||
import type { StopSoundEvent } from '../Events/StopSoundEvent';
|
import type { StopSoundEvent } from "../Events/StopSoundEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import {Sound} from "./Sound/Sound";
|
import { Sound } from "./Sound/Sound";
|
||||||
|
|
||||||
class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
|
export class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
|
||||||
callbacks = []
|
callbacks = [];
|
||||||
|
|
||||||
loadSound(url: string): Sound {
|
loadSound(url: string): Sound {
|
||||||
return new Sound(url);
|
return new Sound(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new WorkadventureSoundCommands();
|
export default new WorkadventureSoundCommands();
|
||||||
|
@ -1,53 +1,55 @@
|
|||||||
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent';
|
import { isButtonClickedEvent } from "../Events/ButtonClickedEvent";
|
||||||
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent';
|
import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent";
|
||||||
import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent';
|
import type { MenuItemRegisterEvent } from "../Events/ui/MenuItemRegisterEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
|
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
|
||||||
import { Popup } from "./Ui/Popup";
|
import { Popup } from "./Ui/Popup";
|
||||||
|
|
||||||
let popupId = 0;
|
let popupId = 0;
|
||||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||||
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>();
|
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
|
||||||
|
number,
|
||||||
|
Map<number, ButtonClickedCallback>
|
||||||
|
>();
|
||||||
|
|
||||||
const menuCallbacks: Map<string, (command: string) => void> = new Map()
|
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
||||||
|
|
||||||
interface ZonedPopupOptions {
|
interface ZonedPopupOptions {
|
||||||
zone: string
|
zone: string;
|
||||||
objectLayerName?: string,
|
objectLayerName?: string;
|
||||||
popupText: string,
|
popupText: string;
|
||||||
delay?: number
|
delay?: number;
|
||||||
popupOptions: Array<ButtonDescriptor>
|
popupOptions: Array<ButtonDescriptor>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
||||||
class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
callbacks = [
|
||||||
|
apiCallback({
|
||||||
callbacks = [apiCallback({
|
type: "buttonClickedEvent",
|
||||||
type: "buttonClickedEvent",
|
typeChecker: isButtonClickedEvent,
|
||||||
typeChecker: isButtonClickedEvent,
|
callback: (payloadData) => {
|
||||||
callback: (payloadData) => {
|
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
||||||
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
const popup = popups.get(payloadData.popupId);
|
||||||
const popup = popups.get(payloadData.popupId);
|
if (popup === undefined) {
|
||||||
if (popup === undefined) {
|
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
|
||||||
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
|
}
|
||||||
}
|
if (callback) {
|
||||||
if (callback) {
|
callback(popup);
|
||||||
callback(popup);
|
}
|
||||||
}
|
},
|
||||||
}
|
}),
|
||||||
}),
|
apiCallback({
|
||||||
apiCallback({
|
type: "menuItemClicked",
|
||||||
type: "menuItemClicked",
|
typeChecker: isMenuItemClickedEvent,
|
||||||
typeChecker: isMenuItemClickedEvent,
|
callback: (event) => {
|
||||||
callback: event => {
|
const callback = menuCallbacks.get(event.menuItem);
|
||||||
const callback = menuCallbacks.get(event.menuItem);
|
if (callback) {
|
||||||
if (callback) {
|
callback(event.menuItem);
|
||||||
callback(event.menuItem)
|
}
|
||||||
}
|
},
|
||||||
}
|
}),
|
||||||
})];
|
];
|
||||||
|
|
||||||
|
|
||||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||||
popupId++;
|
popupId++;
|
||||||
@ -66,40 +68,40 @@ class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiComma
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
'type': 'openPopup',
|
type: "openPopup",
|
||||||
'data': {
|
data: {
|
||||||
popupId,
|
popupId,
|
||||||
targetObject,
|
targetObject,
|
||||||
message,
|
message,
|
||||||
buttons: buttons.map((button) => {
|
buttons: buttons.map((button) => {
|
||||||
return {
|
return {
|
||||||
label: button.label,
|
label: button.label,
|
||||||
className: button.className
|
className: button.className,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
popups.set(popupId, popup)
|
popups.set(popupId, popup);
|
||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
||||||
menuCallbacks.set(commandDescriptor, callback);
|
menuCallbacks.set(commandDescriptor, callback);
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
'type': 'registerMenuCommand',
|
type: "registerMenuCommand",
|
||||||
'data': {
|
data: {
|
||||||
menutItem: commandDescriptor
|
menutItem: commandDescriptor,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBubble(): void {
|
displayBubble(): void {
|
||||||
sendToWorkadventure({ 'type': 'displayBubble', data: null });
|
sendToWorkadventure({ type: "displayBubble", data: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBubble(): void {
|
removeBubble(): void {
|
||||||
sendToWorkadventure({ 'type': 'removeBubble', data: null });
|
sendToWorkadventure({ type: "removeBubble", data: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,12 +10,14 @@
|
|||||||
import {errorStore} from "../Stores/ErrorStore";
|
import {errorStore} from "../Stores/ErrorStore";
|
||||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||||
import LoginScene from "./Login/LoginScene.svelte";
|
import LoginScene from "./Login/LoginScene.svelte";
|
||||||
|
import Chat from "./Chat/Chat.svelte";
|
||||||
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
|
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
|
||||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||||
import {requestVisitCardsStore} from "../Stores/GameStore";
|
import {requestVisitCardsStore} from "../Stores/GameStore";
|
||||||
|
|
||||||
import type {Game} from "../Phaser/Game/Game";
|
import type {Game} from "../Phaser/Game/Game";
|
||||||
|
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 AudioPlaying from "./UI/AudioPlaying.svelte";
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
@ -61,14 +63,6 @@
|
|||||||
<AudioPlaying url={$soundPlayingStore} />
|
<AudioPlaying url={$soundPlayingStore} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!--
|
|
||||||
{#if $menuIconVisible}
|
|
||||||
<div>
|
|
||||||
<MenuIcon />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
-->
|
|
||||||
{#if $gameOverlayVisibilityStore}
|
{#if $gameOverlayVisibilityStore}
|
||||||
<div>
|
<div>
|
||||||
<VideoOverlay></VideoOverlay>
|
<VideoOverlay></VideoOverlay>
|
||||||
@ -94,4 +88,7 @@
|
|||||||
<ErrorDialog></ErrorDialog>
|
<ErrorDialog></ErrorDialog>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $chatVisibilityStore}
|
||||||
|
<Chat></Chat>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
101
front/src/Components/Chat/Chat.svelte
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import { chatMessagesStore, chatVisibilityStore } from "../../Stores/ChatStore";
|
||||||
|
import ChatMessageForm from './ChatMessageForm.svelte';
|
||||||
|
import ChatElement from './ChatElement.svelte';
|
||||||
|
import { afterUpdate, beforeUpdate } from "svelte";
|
||||||
|
|
||||||
|
let listDom: HTMLElement;
|
||||||
|
let autoscroll: boolean;
|
||||||
|
|
||||||
|
beforeUpdate(() => {
|
||||||
|
autoscroll = listDom && (listDom.offsetHeight + listDom.scrollTop) > (listDom.scrollHeight - 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (autoscroll) listDom.scrollTo(0, listDom.scrollHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeChat() {
|
||||||
|
chatVisibilityStore.set(false);
|
||||||
|
}
|
||||||
|
function onKeyDown(e:KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeChat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown}/>
|
||||||
|
|
||||||
|
|
||||||
|
<aside class="chatWindow" transition:fly="{{ x: -1000, duration: 500 }}">
|
||||||
|
<p class="close-icon" on:click={closeChat}>×</p>
|
||||||
|
<section class="messagesList" bind:this={listDom}>
|
||||||
|
<ul>
|
||||||
|
<li><p class="system-text">Here is your chat history: </p></li>
|
||||||
|
{#each $chatMessagesStore as message, i}
|
||||||
|
<li><ChatElement message={message} line={i}></ChatElement></li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section class="messageForm">
|
||||||
|
<ChatMessageForm></ChatMessageForm>
|
||||||
|
</section>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
p.close-icon {
|
||||||
|
position: absolute;
|
||||||
|
padding: 4px;
|
||||||
|
right: 12px;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.system-text {
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding:6px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
max-width: 100%;
|
||||||
|
background: gray;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside.chatWindow {
|
||||||
|
z-index:100;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width:30vw;
|
||||||
|
min-width: 350px;
|
||||||
|
background: rgb(5, 31, 51, 0.9);
|
||||||
|
color: whitesmoke;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
border-bottom-right-radius: 16px;
|
||||||
|
border-top-right-radius: 16px;
|
||||||
|
|
||||||
|
.messagesList {
|
||||||
|
margin-top: 35px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: auto;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.messageForm {
|
||||||
|
flex: 0 70px;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
83
front/src/Components/Chat/ChatElement.svelte
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {ChatMessageTypes} from "../../Stores/ChatStore";
|
||||||
|
import type {ChatMessage} from "../../Stores/ChatStore";
|
||||||
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
|
import ChatPlayerName from './ChatPlayerName.svelte';
|
||||||
|
import type {PlayerInterface} from "../../Phaser/Game/PlayerInterface";
|
||||||
|
|
||||||
|
export let message: ChatMessage;
|
||||||
|
export let line: number;
|
||||||
|
|
||||||
|
$: author = message.author as PlayerInterface;
|
||||||
|
$: targets = message.targets || [];
|
||||||
|
$: texts = message.text || [];
|
||||||
|
|
||||||
|
function urlifyText(text: string): string {
|
||||||
|
return HtmlUtils.urlify(text)
|
||||||
|
}
|
||||||
|
function renderDate(date: Date) {
|
||||||
|
return date.toLocaleTimeString(navigator.language, {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute:'2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function isLastIteration(index: number) {
|
||||||
|
return targets.length -1 === index;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="chatElement">
|
||||||
|
<div class="messagePart">
|
||||||
|
{#if message.type === ChatMessageTypes.userIncoming}
|
||||||
|
>> {#each targets as target, index}<ChatPlayerName player={target} line={line}></ChatPlayerName>{#if !isLastIteration(index)}, {/if}{/each} entered <span class="date">({renderDate(message.date)})</span>
|
||||||
|
{:else if message.type === ChatMessageTypes.userOutcoming}
|
||||||
|
<< {#each targets as target, index}<ChatPlayerName player={target} line={line}></ChatPlayerName>{#if !isLastIteration(index)}, {/if}{/each} left <span class="date">({renderDate(message.date)})</span>
|
||||||
|
{:else if message.type === ChatMessageTypes.me}
|
||||||
|
<h4>Me: <span class="date">({renderDate(message.date)})</span></h4>
|
||||||
|
{#each texts as text}
|
||||||
|
<div><p class="my-text">{@html urlifyText(text)}</p></div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<h4><ChatPlayerName player={author} line={line}></ChatPlayerName>: <span class="date">({renderDate(message.date)})</span></h4>
|
||||||
|
{#each texts as text}
|
||||||
|
<div><p class="other-text">{@html urlifyText(text)}</p></div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
h4, p {
|
||||||
|
font-family: Lato;
|
||||||
|
}
|
||||||
|
div.chatElement {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.messagePart {
|
||||||
|
flex-grow:1;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
span.date {
|
||||||
|
font-size: 80%;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
div > p {
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding:6px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
&.other-text {
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.my-text {
|
||||||
|
background: #6489ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
56
front/src/Components/Chat/ChatMessageForm.svelte
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {chatMessagesStore, chatInputFocusStore} from "../../Stores/ChatStore";
|
||||||
|
|
||||||
|
let newMessageText = '';
|
||||||
|
|
||||||
|
function onFocus() {
|
||||||
|
chatInputFocusStore.set(true);
|
||||||
|
}
|
||||||
|
function onBlur() {
|
||||||
|
chatInputFocusStore.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMessage() {
|
||||||
|
if (!newMessageText) return;
|
||||||
|
chatMessagesStore.addPersonnalMessage(newMessageText);
|
||||||
|
newMessageText = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={saveMessage}>
|
||||||
|
<input type="text" bind:value={newMessageText} placeholder="Enter your message..." on:focus={onFocus} on:blur={onBlur} >
|
||||||
|
<button type="submit">
|
||||||
|
<img src="/static/images/send.png" alt="Send" width="20">
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: auto;
|
||||||
|
background-color: #254560;
|
||||||
|
color: white;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
font-size: 22px;
|
||||||
|
font-family: Lato;
|
||||||
|
padding-left: 6px;
|
||||||
|
min-width: 0; //Needed so that the input doesn't overflow the container in firefox
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #254560;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
border-left: solid white 1px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
51
front/src/Components/Chat/ChatPlayerName.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {PlayerInterface} from "../../Phaser/Game/PlayerInterface";
|
||||||
|
import {chatSubMenuVisbilityStore} from "../../Stores/ChatStore";
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import type {Unsubscriber} from "svelte/store";
|
||||||
|
import ChatSubMenu from "./ChatSubMenu.svelte";
|
||||||
|
|
||||||
|
export let player: PlayerInterface;
|
||||||
|
export let line: number;
|
||||||
|
|
||||||
|
let isSubMenuOpen: boolean;
|
||||||
|
let chatSubMenuVisivilytUnsubcribe: Unsubscriber;
|
||||||
|
|
||||||
|
function openSubMenu() {
|
||||||
|
chatSubMenuVisbilityStore.openSubMenu(player.name, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
chatSubMenuVisivilytUnsubcribe = chatSubMenuVisbilityStore.subscribe((newValue) => {
|
||||||
|
isSubMenuOpen = (newValue === player.name + line);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
chatSubMenuVisivilytUnsubcribe();
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="subMenu">
|
||||||
|
<span class="chatPlayerName" style="color: {player.color || 'white'}" on:click={openSubMenu}>
|
||||||
|
{player.name}
|
||||||
|
</span>
|
||||||
|
{#if isSubMenuOpen}
|
||||||
|
<ChatSubMenu player={player}/>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
span.subMenu {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
span.chatPlayerName {
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
.chatPlayerName:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
33
front/src/Components/Chat/ChatSubMenu.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {PlayerInterface} from "../../Phaser/Game/PlayerInterface";
|
||||||
|
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
||||||
|
|
||||||
|
export let player: PlayerInterface;
|
||||||
|
|
||||||
|
|
||||||
|
function openVisitCard() {
|
||||||
|
if (player.visitCardUrl) {
|
||||||
|
requestVisitCardsStore.set(player.visitCardUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class="selectMenu" style="border-top: {player.color || 'whitesmoke'} 5px solid">
|
||||||
|
<li><button class="text-btn" disabled={!player.visitCardUrl} on:click={openVisitCard}>Visit card</button></li>
|
||||||
|
<li><button class="text-btn" disabled>Add friend</button></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
ul.selectMenu {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { Game } from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
||||||
|
import {activeRowStore} from "../../Stores/CustomCharacterStore";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
||||||
let activeRow = customCharacterScene.activeRow;
|
|
||||||
|
|
||||||
function selectLeft() {
|
function selectLeft() {
|
||||||
customCharacterScene.moveCursorHorizontally(-1);
|
customCharacterScene.moveCursorHorizontally(-1);
|
||||||
@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
function selectUp() {
|
function selectUp() {
|
||||||
customCharacterScene.moveCursorVertically(-1);
|
customCharacterScene.moveCursorVertically(-1);
|
||||||
activeRow = customCharacterScene.activeRow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectDown() {
|
function selectDown() {
|
||||||
customCharacterScene.moveCursorVertically(1);
|
customCharacterScene.moveCursorVertically(1);
|
||||||
activeRow = customCharacterScene.activeRow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function previousScene() {
|
function previousScene() {
|
||||||
@ -44,16 +42,16 @@
|
|||||||
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||||
</section>
|
</section>
|
||||||
<section class="action">
|
<section class="action">
|
||||||
{#if activeRow === 0}
|
{#if $activeRowStore === 0}
|
||||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeRow !== 0}
|
{#if $activeRowStore !== 0}
|
||||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeRow === 5}
|
{#if $activeRowStore === 5}
|
||||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeRow !== 5}
|
{#if $activeRowStore !== 5}
|
||||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
@ -37,9 +37,7 @@
|
|||||||
<img alt="Report this user" src={reportImg}>
|
<img alt="Report this user" src={reportImg}>
|
||||||
<span>Report/Block</span>
|
<span>Report/Block</span>
|
||||||
</button>
|
</button>
|
||||||
{#if $streamStore }
|
|
||||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)}></video>
|
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)}></video>
|
||||||
{/if}
|
|
||||||
<img src={blockSignImg} class="block-logo" alt="Block" />
|
<img src={blockSignImg} class="block-logo" alt="Block" />
|
||||||
{#if $constraintStore && $constraintStore.audio !== false}
|
{#if $constraintStore && $constraintStore.audio !== false}
|
||||||
<SoundMeterWidget stream={$streamStore}></SoundMeterWidget>
|
<SoundMeterWidget stream={$streamStore}></SoundMeterWidget>
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
export function getColorByString(str: string) : string|null {
|
import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer";
|
||||||
|
import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
export function getColorByString(str: string): string | null {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
if (str.length === 0) {
|
if (str.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@ -7,21 +10,37 @@ export function getColorByString(str: string) : string|null {
|
|||||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
hash = hash & hash;
|
hash = hash & hash;
|
||||||
}
|
}
|
||||||
let color = '#';
|
let color = "#";
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const value = (hash >> (i * 8)) & 255;
|
const value = (hash >> (i * 8)) & 255;
|
||||||
color += ('00' + value.toString(16)).substr(-2);
|
color += ("00" + value.toString(16)).substr(-2);
|
||||||
}
|
}
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
export function srcObject(node: HTMLVideoElement, stream: MediaStream | null) {
|
||||||
node.srcObject = stream;
|
node.srcObject = stream;
|
||||||
return {
|
return {
|
||||||
update(newStream: MediaStream) {
|
update(newStream: MediaStream) {
|
||||||
if (node.srcObject != newStream) {
|
if (node.srcObject != newStream) {
|
||||||
node.srcObject = newStream
|
node.srcObject = newStream;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIceServersConfig(user: UserSimplePeerInterface): RTCIceServer[] {
|
||||||
|
const config: RTCIceServer[] = [
|
||||||
|
{
|
||||||
|
urls: STUN_SERVER.split(","),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (TURN_SERVER !== "") {
|
||||||
|
config.push({
|
||||||
|
urls: TURN_SERVER.split(","),
|
||||||
|
username: user.webRtcUser || TURN_USER,
|
||||||
|
credential: user.webRtcPassword || TURN_PASSWORD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,9 @@
|
|||||||
|
|
||||||
.visitCard {
|
.visitCard {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
margin-left: auto;
|
position: absolute;
|
||||||
margin-right: auto;
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
margin-top: 200px;
|
margin-top: 200px;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
|
||||||
|
@ -38,11 +38,9 @@ class ConnectionManager {
|
|||||||
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
|
||||||
localUserStore.saveUser(this.localUser);
|
localUserStore.saveUser(this.localUser);
|
||||||
|
|
||||||
const organizationSlug = data.organizationSlug;
|
const roomUrl = data.roomUrl;
|
||||||
const worldSlug = data.worldSlug;
|
|
||||||
const roomSlug = data.roomSlug;
|
|
||||||
|
|
||||||
const room = new Room('/@/'+organizationSlug+'/'+worldSlug+'/'+roomSlug + window.location.search + window.location.hash);
|
const room = await Room.createRoom(new URL(window.location.protocol + '//' + window.location.host + roomUrl + window.location.search + window.location.hash));
|
||||||
urlManager.pushRoomIdToUrl(room);
|
urlManager.pushRoomIdToUrl(room);
|
||||||
return Promise.resolve(room);
|
return Promise.resolve(room);
|
||||||
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
||||||
@ -66,22 +64,21 @@ class ConnectionManager {
|
|||||||
throw "Error to store local user data";
|
throw "Error to store local user data";
|
||||||
}
|
}
|
||||||
|
|
||||||
let roomId: string;
|
let roomPath: string;
|
||||||
if (connexionType === GameConnexionTypes.empty) {
|
if (connexionType === GameConnexionTypes.empty) {
|
||||||
roomId = START_ROOM_URL;
|
roomPath = window.location.protocol + '//' + window.location.host + START_ROOM_URL;
|
||||||
} else {
|
} else {
|
||||||
roomId = window.location.pathname + window.location.search + window.location.hash;
|
roomPath = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search + window.location.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
//get detail map for anonymous login and set texture in local storage
|
//get detail map for anonymous login and set texture in local storage
|
||||||
const room = new Room(roomId);
|
const room = await Room.createRoom(new URL(roomPath));
|
||||||
const mapDetail = await room.getMapDetail();
|
if(room.textures != undefined && room.textures.length > 0) {
|
||||||
if(mapDetail.textures != undefined && mapDetail.textures.length > 0) {
|
|
||||||
//check if texture was changed
|
//check if texture was changed
|
||||||
if(localUser.textures.length === 0){
|
if(localUser.textures.length === 0){
|
||||||
localUser.textures = mapDetail.textures;
|
localUser.textures = room.textures;
|
||||||
}else{
|
}else{
|
||||||
mapDetail.textures.forEach((newTexture) => {
|
room.textures.forEach((newTexture) => {
|
||||||
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
||||||
if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
|
if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
|
||||||
return;
|
return;
|
||||||
@ -114,9 +111,9 @@ class ConnectionManager {
|
|||||||
this.localUser = new LocalUser('', 'test', []);
|
this.localUser = new LocalUser('', 'test', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null): Promise<OnConnectInterface> {
|
public connectToRoomSocket(roomUrl: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null): Promise<OnConnectInterface> {
|
||||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||||
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport, companion);
|
const connection = new RoomConnection(this.localUser.jwtToken, roomUrl, name, characterLayers, position, viewport, companion);
|
||||||
connection.onConnectError((error: object) => {
|
connection.onConnectError((error: object) => {
|
||||||
console.log('An error occurred while connecting to socket server. Retrying');
|
console.log('An error occurred while connecting to socket server. Retrying');
|
||||||
reject(error);
|
reject(error);
|
||||||
@ -137,7 +134,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(roomId, name, characterLayers, position, viewport, companion).then((connection) => resolve(connection));
|
this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then((connection) => resolve(connection));
|
||||||
}, 4000 + Math.floor(Math.random() * 2000) );
|
}, 4000 + Math.floor(Math.random() * 2000) );
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
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";
|
||||||
|
|
||||||
export enum EventMessage{
|
export enum EventMessage {
|
||||||
CONNECT = "connect",
|
CONNECT = "connect",
|
||||||
WEBRTC_SIGNAL = "webrtc-signal",
|
WEBRTC_SIGNAL = "webrtc-signal",
|
||||||
WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal",
|
WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal",
|
||||||
@ -17,7 +17,7 @@ export enum EventMessage{
|
|||||||
GROUP_CREATE_UPDATE = "group-create-update",
|
GROUP_CREATE_UPDATE = "group-create-update",
|
||||||
GROUP_DELETE = "group-delete",
|
GROUP_DELETE = "group-delete",
|
||||||
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
||||||
ITEM_EVENT = 'item-event',
|
ITEM_EVENT = "item-event",
|
||||||
|
|
||||||
CONNECT_ERROR = "connect_error",
|
CONNECT_ERROR = "connect_error",
|
||||||
CONNECTING_ERROR = "connecting_error",
|
CONNECTING_ERROR = "connecting_error",
|
||||||
@ -36,7 +36,7 @@ export enum EventMessage{
|
|||||||
export interface PointInterface {
|
export interface PointInterface {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
direction : string;
|
direction: string;
|
||||||
moving: boolean;
|
moving: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +45,9 @@ export interface MessageUserPositionInterface {
|
|||||||
name: string;
|
name: string;
|
||||||
characterLayers: BodyResourceDescriptionInterface[];
|
characterLayers: BodyResourceDescriptionInterface[];
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
visitCardUrl: string|null;
|
visitCardUrl: string | null;
|
||||||
companion: string|null;
|
companion: string | null;
|
||||||
|
userUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageUserMovedInterface {
|
export interface MessageUserMovedInterface {
|
||||||
@ -60,58 +61,59 @@ export interface MessageUserJoined {
|
|||||||
characterLayers: BodyResourceDescriptionInterface[];
|
characterLayers: BodyResourceDescriptionInterface[];
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
visitCardUrl: string | null;
|
visitCardUrl: string | null;
|
||||||
companion: string|null;
|
companion: string | null;
|
||||||
|
userUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
x: number,
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupCreatedUpdatedMessageInterface {
|
export interface GroupCreatedUpdatedMessageInterface {
|
||||||
position: PositionInterface,
|
position: PositionInterface;
|
||||||
groupId: number,
|
groupId: number;
|
||||||
groupSize: number
|
groupSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebRtcDisconnectMessageInterface {
|
export interface WebRtcDisconnectMessageInterface {
|
||||||
userId: number
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebRtcSignalReceivedMessageInterface {
|
export interface WebRtcSignalReceivedMessageInterface {
|
||||||
userId: number,
|
userId: number;
|
||||||
signal: SignalData,
|
signal: SignalData;
|
||||||
webRtcUser: string | undefined,
|
webRtcUser: string | undefined;
|
||||||
webRtcPassword: string | undefined
|
webRtcPassword: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewportInterface {
|
export interface ViewportInterface {
|
||||||
left: number,
|
left: number;
|
||||||
top: number,
|
top: number;
|
||||||
right: number,
|
right: number;
|
||||||
bottom: number,
|
bottom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemEventMessageInterface {
|
export interface ItemEventMessageInterface {
|
||||||
itemId: number,
|
itemId: number;
|
||||||
event: string,
|
event: string;
|
||||||
state: unknown,
|
state: unknown;
|
||||||
parameters: unknown
|
parameters: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RoomJoinedMessageInterface {
|
export interface RoomJoinedMessageInterface {
|
||||||
//users: MessageUserPositionInterface[],
|
//users: MessageUserPositionInterface[],
|
||||||
//groups: GroupCreatedUpdatedMessageInterface[],
|
//groups: GroupCreatedUpdatedMessageInterface[],
|
||||||
items: { [itemId: number] : unknown }
|
items: { [itemId: number]: unknown };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlayGlobalMessageInterface {
|
export interface PlayGlobalMessageInterface {
|
||||||
id: string
|
id: string;
|
||||||
type: string
|
type: string;
|
||||||
message: string
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnConnectInterface {
|
export interface OnConnectInterface {
|
||||||
connection: RoomConnection,
|
connection: RoomConnection;
|
||||||
room: RoomJoinedMessageInterface
|
room: RoomJoinedMessageInterface;
|
||||||
}
|
}
|
||||||
|
@ -1,90 +1,105 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
import { PUSHER_URL } from "../Enum/EnvironmentVariable";
|
||||||
import type {CharacterTexture} from "./LocalUser";
|
import type { CharacterTexture } from "./LocalUser";
|
||||||
|
|
||||||
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) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoomRedirect {
|
||||||
|
redirectUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Room {
|
export class Room {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private mapUrl: string|undefined;
|
private _mapUrl: string | undefined;
|
||||||
private textures: CharacterTexture[]|undefined;
|
private _textures: CharacterTexture[] | undefined;
|
||||||
private instance: string|undefined;
|
private instance: string | undefined;
|
||||||
private _search: URLSearchParams;
|
private readonly _search: URLSearchParams;
|
||||||
|
|
||||||
constructor(id: string) {
|
private constructor(private roomUrl: URL) {
|
||||||
const url = new URL(id, 'https://example.com');
|
this.id = roomUrl.pathname;
|
||||||
|
|
||||||
this.id = url.pathname;
|
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.isPublic = true;
|
this.isPublic = true;
|
||||||
} else if (this.id.startsWith('@/')) {
|
} else if (this.id.startsWith("@/")) {
|
||||||
this.isPublic = false;
|
this.isPublic = false;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid room ID');
|
throw new Error("Invalid room ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
this._search = new URLSearchParams(url.search);
|
this._search = new URLSearchParams(roomUrl.search);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
|
/**
|
||||||
let roomId = '';
|
* Creates a "Room" object representing the room.
|
||||||
let hash = '';
|
* This method will follow room redirects if necessary, so the instance returned is a "real" room.
|
||||||
if (!identifier.startsWith('/_/') && !identifier.startsWith('/@/')) { //relative file link
|
*/
|
||||||
//Relative identifier can be deep enough to rewrite the base domain, so we cannot use the variable 'baseUrl' as the actual base url for the URL objects.
|
public static async createRoom(roomUrl: URL): Promise<Room> {
|
||||||
//We instead use 'workadventure' as a dummy base value.
|
let redirectCount = 0;
|
||||||
const baseUrlObject = new URL(baseUrl);
|
while (redirectCount < 32) {
|
||||||
const absoluteExitSceneUrl = new URL(identifier, 'http://workadventure/_/'+currentInstance+'/'+baseUrlObject.hostname+baseUrlObject.pathname);
|
const room = new Room(roomUrl);
|
||||||
roomId = absoluteExitSceneUrl.pathname; //in case of a relative url, we need to create a public roomId
|
const result = await room.getMapDetail();
|
||||||
roomId = roomId.substring(1); //remove the leading slash
|
if (result instanceof MapDetail) {
|
||||||
hash = absoluteExitSceneUrl.hash;
|
return room;
|
||||||
hash = hash.substring(1); //remove the leading diese
|
|
||||||
} else { //absolute room Id
|
|
||||||
const parts = identifier.split('#');
|
|
||||||
roomId = parts[0];
|
|
||||||
roomId = roomId.substring(1); //remove the leading slash
|
|
||||||
if (parts.length > 1) {
|
|
||||||
hash = parts[1]
|
|
||||||
}
|
}
|
||||||
|
redirectCount++;
|
||||||
|
roomUrl = new URL(result.redirectUrl);
|
||||||
}
|
}
|
||||||
return {roomId, hash}
|
throw new Error("Room resolving seems stuck in a redirect loop after 32 redirect attempts");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMapDetail(): Promise<MapDetail> {
|
public static getRoomPathFromExitUrl(exitUrl: string, currentRoomUrl: string): URL {
|
||||||
return new Promise<MapDetail>((resolve, reject) => {
|
const url = new URL(exitUrl, currentRoomUrl);
|
||||||
if (this.mapUrl !== undefined && this.textures != undefined) {
|
return url;
|
||||||
resolve(new MapDetail(this.mapUrl, this.textures));
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isPublic) {
|
/**
|
||||||
const match = /_\/[^/]+\/(.+)/.exec(this.id);
|
* @deprecated USage of exitSceneUrl is deprecated and therefore, this method is deprecated too.
|
||||||
if (!match) throw new Error('Could not extract url from "'+this.id+'"');
|
*/
|
||||||
this.mapUrl = window.location.protocol+'//'+match[1];
|
public static getRoomPathFromExitSceneUrl(
|
||||||
resolve(new MapDetail(this.mapUrl, this.textures));
|
exitSceneUrl: string,
|
||||||
return;
|
currentRoomUrl: string,
|
||||||
} else {
|
currentMapUrl: string
|
||||||
// We have a private ID, we need to query the map URL from the server.
|
): URL {
|
||||||
const urlParts = this.parsePrivateUrl(this.id);
|
const absoluteExitSceneUrl = new URL(exitSceneUrl, currentMapUrl);
|
||||||
|
const baseUrl = new URL(currentRoomUrl);
|
||||||
|
|
||||||
Axios.get(`${PUSHER_URL}/map`, {
|
const currentRoom = new Room(baseUrl);
|
||||||
params: urlParts
|
let instance: string = "global";
|
||||||
}).then(({data}) => {
|
if (currentRoom.isPublic) {
|
||||||
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
instance = currentRoom.instance as string;
|
||||||
resolve(data);
|
}
|
||||||
return;
|
|
||||||
}).catch((reason) => {
|
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
||||||
reject(reason);
|
if (absoluteExitSceneUrl.hash) {
|
||||||
});
|
baseUrl.hash = absoluteExitSceneUrl.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getMapDetail(): Promise<MapDetail | RoomRedirect> {
|
||||||
|
const result = await Axios.get(`${PUSHER_URL}/map`, {
|
||||||
|
params: {
|
||||||
|
playUri: this.roomUrl.toString(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const data = result.data;
|
||||||
|
if (data.redirectUrl) {
|
||||||
|
return {
|
||||||
|
redirectUrl: data.redirectUrl as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||||
|
this._mapUrl = data.mapUrl;
|
||||||
|
this._textures = data.textures;
|
||||||
|
return new MapDetail(data.mapUrl, data.textures);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,37 +114,39 @@ 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;
|
||||||
} else {
|
} else {
|
||||||
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]+'/'+match[2];
|
this.instance = match[1] + "/" + match[2];
|
||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private parsePrivateUrl(url: string): { organizationSlug: string, worldSlug: string, roomSlug?: string } {
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
private parsePrivateUrl(url: string): { organizationSlug: string; worldSlug: string; roomSlug?: string } {
|
||||||
const regex = /@\/([^/]+)\/([^/]+)(?:\/([^/]*))?/gm;
|
const regex = /@\/([^/]+)\/([^/]+)(?:\/([^/]*))?/gm;
|
||||||
const match = regex.exec(url);
|
const match = regex.exec(url);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new Error('Invalid URL '+url);
|
throw new Error("Invalid URL " + url);
|
||||||
}
|
}
|
||||||
const results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
const results: { organizationSlug: string; worldSlug: string; roomSlug?: string } = {
|
||||||
organizationSlug: match[1],
|
organizationSlug: match[1],
|
||||||
worldSlug: match[2],
|
worldSlug: match[2],
|
||||||
}
|
};
|
||||||
if (match[3] !== undefined) {
|
if (match[3] !== undefined) {
|
||||||
results.roomSlug = match[3];
|
results.roomSlug = match[3];
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isDisconnected(): boolean
|
public isDisconnected(): boolean {
|
||||||
{
|
const alone = this._search.get("alone");
|
||||||
const alone = this._search.get('alone');
|
if (alone && alone !== "0" && alone.toLowerCase() !== "false") {
|
||||||
if (alone && alone !== '0' && alone.toLowerCase() !== 'false') {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -138,4 +155,33 @@ export class Room {
|
|||||||
public get search(): URLSearchParams {
|
public get search(): URLSearchParams {
|
||||||
return this._search;
|
return this._search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2 rooms are equal if they share the same path (but not necessarily the same hash)
|
||||||
|
* @param room
|
||||||
|
*/
|
||||||
|
public isEqual(room: Room): boolean {
|
||||||
|
return room.key === this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key representing this room
|
||||||
|
*/
|
||||||
|
public get key(): string {
|
||||||
|
const newUrl = new URL(this.roomUrl.toString());
|
||||||
|
newUrl.search = "";
|
||||||
|
newUrl.hash = "";
|
||||||
|
return newUrl.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
get textures(): CharacterTexture[] | undefined {
|
||||||
|
return this._textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mapUrl(): string {
|
||||||
|
if (!this._mapUrl) {
|
||||||
|
throw new Error("Map URL not fetched yet");
|
||||||
|
}
|
||||||
|
return this._mapUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ import {
|
|||||||
RoomJoinedMessage,
|
RoomJoinedMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
SilentMessage, StopGlobalMessage,
|
SilentMessage,
|
||||||
|
StopGlobalMessage,
|
||||||
UserJoinedMessage,
|
UserJoinedMessage,
|
||||||
UserLeftMessage,
|
UserLeftMessage,
|
||||||
UserMovedMessage,
|
UserMovedMessage,
|
||||||
@ -31,17 +32,22 @@ import {
|
|||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
SendUserMessage,
|
SendUserMessage,
|
||||||
BanUserMessage,
|
BanUserMessage,
|
||||||
} from "../Messages/generated/messages_pb"
|
} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
|
import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
|
||||||
import {
|
import {
|
||||||
EventMessage,
|
EventMessage,
|
||||||
GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface,
|
GroupCreatedUpdatedMessageInterface,
|
||||||
MessageUserJoined, OnConnectInterface, PlayGlobalMessageInterface, PositionInterface,
|
ItemEventMessageInterface,
|
||||||
|
MessageUserJoined,
|
||||||
|
OnConnectInterface,
|
||||||
|
PlayGlobalMessageInterface,
|
||||||
|
PositionInterface,
|
||||||
RoomJoinedMessageInterface,
|
RoomJoinedMessageInterface,
|
||||||
ViewportInterface, WebRtcDisconnectMessageInterface,
|
ViewportInterface,
|
||||||
|
WebRtcDisconnectMessageInterface,
|
||||||
WebRtcSignalReceivedMessageInterface,
|
WebRtcSignalReceivedMessageInterface,
|
||||||
} from "./ConnexionModels";
|
} from "./ConnexionModels";
|
||||||
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
||||||
@ -61,36 +67,45 @@ export class RoomConnection implements RoomConnection {
|
|||||||
private closed: boolean = false;
|
private closed: boolean = false;
|
||||||
private tags: string[] = [];
|
private tags: string[] = [];
|
||||||
|
|
||||||
public static setWebsocketFactory(websocketFactory: (url: string) => any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public static setWebsocketFactory(websocketFactory: (url: string) => any): void {
|
||||||
RoomConnection.websocketFactory = websocketFactory;
|
RoomConnection.websocketFactory = websocketFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param token A JWT token containing the UUID of the user
|
* @param token A JWT token containing the UUID of the user
|
||||||
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
|
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
|
||||||
*/
|
*/
|
||||||
public constructor(token: string | null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string | null) {
|
public constructor(
|
||||||
|
token: string | null,
|
||||||
|
roomUrl: string,
|
||||||
|
name: string,
|
||||||
|
characterLayers: string[],
|
||||||
|
position: PositionInterface,
|
||||||
|
viewport: ViewportInterface,
|
||||||
|
companion: string | null
|
||||||
|
) {
|
||||||
let url = new URL(PUSHER_URL, window.location.toString()).toString();
|
let url = new URL(PUSHER_URL, window.location.toString()).toString();
|
||||||
url = url.replace('http://', 'ws://').replace('https://', 'wss://');
|
url = url.replace("http://", "ws://").replace("https://", "wss://");
|
||||||
if (!url.endsWith('/')) {
|
if (!url.endsWith("/")) {
|
||||||
url += '/';
|
url += "/";
|
||||||
}
|
}
|
||||||
url += 'room';
|
url += "room";
|
||||||
url += '?roomId=' + (roomId ? encodeURIComponent(roomId) : '');
|
url += "?roomId=" + encodeURIComponent(roomUrl);
|
||||||
url += '&token=' + (token ? encodeURIComponent(token) : '');
|
url += "&token=" + (token ? encodeURIComponent(token) : "");
|
||||||
url += '&name=' + encodeURIComponent(name);
|
url += "&name=" + encodeURIComponent(name);
|
||||||
for (const layer of characterLayers) {
|
for (const layer of characterLayers) {
|
||||||
url += '&characterLayers=' + encodeURIComponent(layer);
|
url += "&characterLayers=" + encodeURIComponent(layer);
|
||||||
}
|
}
|
||||||
url += '&x=' + Math.floor(position.x);
|
url += "&x=" + Math.floor(position.x);
|
||||||
url += '&y=' + Math.floor(position.y);
|
url += "&y=" + Math.floor(position.y);
|
||||||
url += '&top=' + Math.floor(viewport.top);
|
url += "&top=" + Math.floor(viewport.top);
|
||||||
url += '&bottom=' + Math.floor(viewport.bottom);
|
url += "&bottom=" + Math.floor(viewport.bottom);
|
||||||
url += '&left=' + Math.floor(viewport.left);
|
url += "&left=" + Math.floor(viewport.left);
|
||||||
url += '&right=' + Math.floor(viewport.right);
|
url += "&right=" + Math.floor(viewport.right);
|
||||||
if (typeof companion === 'string') {
|
if (typeof companion === "string") {
|
||||||
url += '&companion=' + encodeURIComponent(companion);
|
url += "&companion=" + encodeURIComponent(companion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RoomConnection.websocketFactory) {
|
if (RoomConnection.websocketFactory) {
|
||||||
@ -99,7 +114,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket = new WebSocket(url);
|
this.socket = new WebSocket(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socket.binaryType = 'arraybuffer';
|
this.socket.binaryType = "arraybuffer";
|
||||||
|
|
||||||
let interval: ReturnType<typeof setInterval> | undefined = undefined;
|
let interval: ReturnType<typeof setInterval> | undefined = undefined;
|
||||||
|
|
||||||
@ -109,7 +124,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
interval = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
|
interval = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.addEventListener('close', (event) => {
|
this.socket.addEventListener("close", (event) => {
|
||||||
if (interval) {
|
if (interval) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
@ -126,7 +141,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
|
|
||||||
if (message.hasBatchmessage()) {
|
if (message.hasBatchmessage()) {
|
||||||
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
||||||
let event: string|null = null;
|
let event: string | null = null;
|
||||||
let payload;
|
let payload;
|
||||||
if (subMessage.hasUsermovedmessage()) {
|
if (subMessage.hasUsermovedmessage()) {
|
||||||
event = EventMessage.USER_MOVED;
|
event = EventMessage.USER_MOVED;
|
||||||
@ -150,7 +165,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage;
|
const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected batch message type');
|
throw new Error("Unexpected batch message type");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
@ -171,8 +186,8 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.dispatch(EventMessage.CONNECT, {
|
this.dispatch(EventMessage.CONNECT, {
|
||||||
connection: this,
|
connection: this,
|
||||||
room: {
|
room: {
|
||||||
items
|
items,
|
||||||
} as RoomJoinedMessageInterface
|
} as RoomJoinedMessageInterface,
|
||||||
});
|
});
|
||||||
} else if (message.hasWorldfullmessage()) {
|
} else if (message.hasWorldfullmessage()) {
|
||||||
worldFullMessageStream.onMessage();
|
worldFullMessageStream.onMessage();
|
||||||
@ -183,7 +198,10 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||||
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
||||||
this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage());
|
this.dispatch(
|
||||||
|
EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL,
|
||||||
|
message.getWebrtcscreensharingsignaltoclientmessage()
|
||||||
|
);
|
||||||
} else if (message.hasWebrtcstartmessage()) {
|
} else if (message.hasWebrtcstartmessage()) {
|
||||||
this.dispatch(EventMessage.WEBRTC_START, message.getWebrtcstartmessage());
|
this.dispatch(EventMessage.WEBRTC_START, message.getWebrtcstartmessage());
|
||||||
} else if (message.hasWebrtcdisconnectmessage()) {
|
} else if (message.hasWebrtcdisconnectmessage()) {
|
||||||
@ -205,10 +223,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (message.hasRefreshroommessage()) {
|
} else if (message.hasRefreshroommessage()) {
|
||||||
//todo: implement a way to notify the user the room was refreshed.
|
//todo: implement a way to notify the user the room was refreshed.
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown message received');
|
throw new Error("Unknown message received");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private dispatch(event: string, payload: unknown): void {
|
private dispatch(event: string, payload: unknown): void {
|
||||||
@ -243,16 +260,16 @@ export class RoomConnection implements RoomConnection {
|
|||||||
positionMessage.setY(Math.floor(y));
|
positionMessage.setY(Math.floor(y));
|
||||||
let directionEnum: Direction;
|
let directionEnum: Direction;
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case 'up':
|
case "up":
|
||||||
directionEnum = Direction.UP;
|
directionEnum = Direction.UP;
|
||||||
break;
|
break;
|
||||||
case 'down':
|
case "down":
|
||||||
directionEnum = Direction.DOWN;
|
directionEnum = Direction.DOWN;
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case "left":
|
||||||
directionEnum = Direction.LEFT;
|
directionEnum = Direction.LEFT;
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case "right":
|
||||||
directionEnum = Direction.RIGHT;
|
directionEnum = Direction.RIGHT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -327,15 +344,17 @@ export class RoomConnection implements RoomConnection {
|
|||||||
private toMessageUserJoined(message: UserJoinedMessage): MessageUserJoined {
|
private toMessageUserJoined(message: UserJoinedMessage): MessageUserJoined {
|
||||||
const position = message.getPosition();
|
const position = message.getPosition();
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Invalid JOIN_ROOM message');
|
throw new Error("Invalid JOIN_ROOM message");
|
||||||
}
|
}
|
||||||
|
|
||||||
const characterLayers = message.getCharacterlayersList().map((characterLayer: CharacterLayerMessage): BodyResourceDescriptionInterface => {
|
const characterLayers = message
|
||||||
return {
|
.getCharacterlayersList()
|
||||||
name: characterLayer.getName(),
|
.map((characterLayer: CharacterLayerMessage): BodyResourceDescriptionInterface => {
|
||||||
img: characterLayer.getUrl()
|
return {
|
||||||
}
|
name: characterLayer.getName(),
|
||||||
})
|
img: characterLayer.getUrl(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const companion = message.getCompanion();
|
const companion = message.getCompanion();
|
||||||
|
|
||||||
@ -345,8 +364,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
characterLayers,
|
characterLayers,
|
||||||
visitCardUrl: message.getVisitcardurl(),
|
visitCardUrl: message.getVisitcardurl(),
|
||||||
position: ProtobufClientUtils.toPointInterface(position),
|
position: ProtobufClientUtils.toPointInterface(position),
|
||||||
companion: companion ? companion.getName() : null
|
companion: companion ? companion.getName() : null,
|
||||||
}
|
userUuid: message.getUseruuid(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUserMoved(callback: (message: UserMovedMessage) => void): void {
|
public onUserMoved(callback: (message: UserMovedMessage) => void): void {
|
||||||
@ -372,7 +392,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void {
|
public onGroupUpdatedOrCreated(
|
||||||
|
callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void
|
||||||
|
): void {
|
||||||
this.onMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => {
|
this.onMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => {
|
||||||
callback(this.toGroupCreatedUpdatedMessage(message));
|
callback(this.toGroupCreatedUpdatedMessage(message));
|
||||||
});
|
});
|
||||||
@ -381,14 +403,14 @@ export class RoomConnection implements RoomConnection {
|
|||||||
private toGroupCreatedUpdatedMessage(message: GroupUpdateMessage): GroupCreatedUpdatedMessageInterface {
|
private toGroupCreatedUpdatedMessage(message: GroupUpdateMessage): GroupCreatedUpdatedMessageInterface {
|
||||||
const position = message.getPosition();
|
const position = message.getPosition();
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Missing position in GROUP_CREATE_UPDATE');
|
throw new Error("Missing position in GROUP_CREATE_UPDATE");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupId: message.getGroupid(),
|
groupId: message.getGroupid(),
|
||||||
position: position.toObject(),
|
position: position.toObject(),
|
||||||
groupSize: message.getGroupsize()
|
groupSize: message.getGroupsize(),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGroupDeleted(callback: (groupId: number) => void): void {
|
public onGroupDeleted(callback: (groupId: number) => void): void {
|
||||||
@ -404,7 +426,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onConnectError(callback: (error: Event) => void): void {
|
public onConnectError(callback: (error: Event) => void): void {
|
||||||
this.socket.addEventListener('error', callback)
|
this.socket.addEventListener("error", callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
||||||
@ -445,7 +467,6 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => {
|
this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => {
|
||||||
callback({
|
callback({
|
||||||
userId: message.getUserid(),
|
userId: message.getUserid(),
|
||||||
name: message.getName(),
|
|
||||||
initiator: message.getInitiator(),
|
initiator: message.getInitiator(),
|
||||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||||
@ -476,11 +497,11 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onServerDisconnected(callback: () => void): void {
|
public onServerDisconnected(callback: () => void): void {
|
||||||
this.socket.addEventListener('close', (event) => {
|
this.socket.addEventListener("close", (event) => {
|
||||||
if (this.closed === true || connectionManager.unloading) {
|
if (this.closed === true || connectionManager.unloading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Socket closed with code ' + event.code + ". Reason: " + event.reason);
|
console.log("Socket closed with code " + event.code + ". Reason: " + event.reason);
|
||||||
if (event.code === 1000) {
|
if (event.code === 1000) {
|
||||||
// Normal closure case
|
// Normal closure case
|
||||||
return;
|
return;
|
||||||
@ -490,14 +511,14 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getUserId(): number {
|
public getUserId(): number {
|
||||||
if (this.userId === null) throw 'UserId cannot be null!'
|
if (this.userId === null) throw "UserId cannot be null!";
|
||||||
return this.userId;
|
return this.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void {
|
disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void {
|
||||||
this.onMessage(EventMessage.WEBRTC_DISCONNECT, (message: WebRtcDisconnectMessage) => {
|
this.onMessage(EventMessage.WEBRTC_DISCONNECT, (message: WebRtcDisconnectMessage) => {
|
||||||
callback({
|
callback({
|
||||||
userId: message.getUserid()
|
userId: message.getUserid(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -521,21 +542,22 @@ export class RoomConnection implements RoomConnection {
|
|||||||
itemId: message.getItemid(),
|
itemId: message.getItemid(),
|
||||||
event: message.getEvent(),
|
event: message.getEvent(),
|
||||||
parameters: JSON.parse(message.getParametersjson()),
|
parameters: JSON.parse(message.getParametersjson()),
|
||||||
state: JSON.parse(message.getStatejson())
|
state: JSON.parse(message.getStatejson()),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadAudio(file: FormData) {
|
public uploadAudio(file: FormData) {
|
||||||
return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file).then((res: { data: {} }) => {
|
return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file)
|
||||||
return res.data;
|
.then((res: { data: {} }) => {
|
||||||
}).catch((err) => {
|
return res.data;
|
||||||
console.error(err);
|
})
|
||||||
throw err;
|
.catch((err) => {
|
||||||
});
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) {
|
public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) {
|
||||||
return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => {
|
return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => {
|
||||||
callback({
|
callback({
|
||||||
@ -570,9 +592,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitReportPlayerMessage(reportedUserId: number, reportComment: string): void {
|
public emitReportPlayerMessage(reportedUserUuid: string, reportComment: string): void {
|
||||||
const reportPlayerMessage = new ReportPlayerMessage();
|
const reportPlayerMessage = new ReportPlayerMessage();
|
||||||
reportPlayerMessage.setReporteduserid(reportedUserId);
|
reportPlayerMessage.setReporteduseruuid(reportedUserUuid);
|
||||||
reportPlayerMessage.setReportcomment(reportComment);
|
reportPlayerMessage.setReportcomment(reportComment);
|
||||||
|
|
||||||
const clientToServerMessage = new ClientToServerMessage();
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
@ -605,12 +627,12 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isAdmin(): boolean {
|
public isAdmin(): boolean {
|
||||||
return this.hasTag('admin');
|
return this.hasTag("admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitEmoteEvent(emoteName: string): void {
|
public emitEmoteEvent(emoteName: string): void {
|
||||||
const emoteMessage = new EmotePromptMessage();
|
const emoteMessage = new EmotePromptMessage();
|
||||||
emoteMessage.setEmote(emoteName)
|
emoteMessage.setEmote(emoteName);
|
||||||
|
|
||||||
const clientToServerMessage = new ClientToServerMessage();
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
clientToServerMessage.setEmotepromptmessage(emoteMessage);
|
clientToServerMessage.setEmotepromptmessage(emoteMessage);
|
||||||
@ -618,7 +640,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllTags() : string[] {
|
public getAllTags(): string[] {
|
||||||
return this.tags;
|
return this.tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||||
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||||
|
|
||||||
export const openChatIconName = 'openChatIcon';
|
export const openChatIconName = "openChatIcon";
|
||||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, openChatIconName, 3);
|
super(scene, x, y, openChatIconName, 3);
|
||||||
@ -9,9 +9,9 @@ export class OpenChatIcon extends Phaser.GameObjects.Image {
|
|||||||
this.setScrollFactor(0, 0);
|
this.setScrollFactor(0, 0);
|
||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
//this.setVisible(false);
|
||||||
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
|
|
||||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
this.on("pointerup", () => chatVisibilityStore.set(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,6 @@ export class TextUtils {
|
|||||||
options.align = object.text.halign;
|
options.align = object.text.halign;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(options);
|
|
||||||
const textElem = scene.add.text(object.x, object.y, object.text.text, options);
|
const textElem = scene.add.text(object.x, object.y, object.text.text, options);
|
||||||
textElem.setAngle(object.rotation);
|
textElem.setAngle(object.rotation);
|
||||||
}
|
}
|
||||||
|
@ -1,90 +1,123 @@
|
|||||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||||
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
|
||||||
|
|
||||||
export interface FrameConfig {
|
export interface FrameConfig {
|
||||||
frameWidth: number,
|
frameWidth: number;
|
||||||
frameHeight: number,
|
frameHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
||||||
const returnArray:BodyResourceDescriptionInterface[][] = [];
|
const returnArray: BodyResourceDescriptionInterface[][] = [];
|
||||||
LAYERS.forEach(layer => {
|
LAYERS.forEach((layer) => {
|
||||||
const layerArray:BodyResourceDescriptionInterface[] = [];
|
const layerArray: BodyResourceDescriptionInterface[] = [];
|
||||||
Object.values(layer).forEach((textureDescriptor) => {
|
Object.values(layer).forEach((textureDescriptor) => {
|
||||||
layerArray.push(textureDescriptor);
|
layerArray.push(textureDescriptor);
|
||||||
load.spritesheet(textureDescriptor.name,textureDescriptor.img,{frameWidth: 32, frameHeight: 32});
|
load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
|
||||||
})
|
});
|
||||||
returnArray.push(layerArray)
|
returnArray.push(layerArray);
|
||||||
});
|
});
|
||||||
return returnArray;
|
return returnArray;
|
||||||
}
|
};
|
||||||
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
|
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
|
||||||
const returnArray = Object.values(PLAYER_RESOURCES);
|
const returnArray = Object.values(PLAYER_RESOURCES);
|
||||||
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
|
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
|
||||||
load.spritesheet(playerResource.name, playerResource.img, {frameWidth: 32, frameHeight: 32});
|
load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 });
|
||||||
});
|
});
|
||||||
return returnArray;
|
return returnArray;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
export const loadCustomTexture = (
|
||||||
const name = 'customCharacterTexture'+texture.id;
|
loaderPlugin: LoaderPlugin,
|
||||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
texture: CharacterTexture
|
||||||
|
): Promise<BodyResourceDescriptionInterface> => {
|
||||||
|
const name = "customCharacterTexture" + texture.id;
|
||||||
|
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
|
||||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||||
frameWidth: 32,
|
frameWidth: 32,
|
||||||
frameHeight: 32
|
frameHeight: 32,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
export const lazyLoadPlayerCharacterTextures = (
|
||||||
const promisesList:Promise<unknown>[] = [];
|
loadPlugin: LoaderPlugin,
|
||||||
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
|
texturekeys: Array<string | BodyResourceDescriptionInterface>
|
||||||
|
): Promise<string[]> => {
|
||||||
|
const promisesList: Promise<unknown>[] = [];
|
||||||
|
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
|
||||||
try {
|
try {
|
||||||
//TODO refactor
|
//TODO refactor
|
||||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||||
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, {
|
promisesList.push(
|
||||||
frameWidth: 32,
|
createLoadingPromise(loadPlugin, playerResourceDescriptor, {
|
||||||
frameHeight: 32
|
frameWidth: 32,
|
||||||
}));
|
frameHeight: 32,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}catch (err){
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
|
let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>;
|
||||||
if (promisesList.length > 0) {
|
if (promisesList.length > 0) {
|
||||||
loadPlugin.start();
|
loadPlugin.start();
|
||||||
returnPromise = Promise.all(promisesList).then(() => texturekeys);
|
returnPromise = Promise.all(promisesList).then(() => texturekeys);
|
||||||
} else {
|
} else {
|
||||||
returnPromise = Promise.resolve(texturekeys);
|
returnPromise = Promise.resolve(texturekeys);
|
||||||
}
|
}
|
||||||
return returnPromise.then((keys) => keys.map((key) => {
|
|
||||||
return typeof key !== 'string' ? key.name : key;
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptionInterface): BodyResourceDescriptionInterface => {
|
//If the loading fail, we render the default model instead.
|
||||||
if (typeof textureKey !== 'string' && textureKey.img) {
|
return returnPromise
|
||||||
|
.then((keys) =>
|
||||||
|
keys.map((key) => {
|
||||||
|
return typeof key !== "string" ? key.name : key;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(() => lazyLoadPlayerCharacterTextures(loadPlugin, ["color_22", "eyes_23"]));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRessourceDescriptor = (
|
||||||
|
textureKey: string | BodyResourceDescriptionInterface
|
||||||
|
): BodyResourceDescriptionInterface => {
|
||||||
|
if (typeof textureKey !== "string" && textureKey.img) {
|
||||||
return textureKey;
|
return textureKey;
|
||||||
}
|
}
|
||||||
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
|
const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name;
|
||||||
const playerResource = PLAYER_RESOURCES[textureName];
|
const playerResource = PLAYER_RESOURCES[textureName];
|
||||||
if (playerResource !== undefined) return playerResource;
|
if (playerResource !== undefined) return playerResource;
|
||||||
|
|
||||||
for (let i=0; i<LAYERS.length;i++) {
|
for (let i = 0; i < LAYERS.length; i++) {
|
||||||
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 "Could not find a data for texture " + textureName;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig) => {
|
export const createLoadingPromise = (
|
||||||
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
loadPlugin: LoaderPlugin,
|
||||||
|
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
||||||
|
frameConfig: FrameConfig
|
||||||
|
) => {
|
||||||
|
return new Promise<BodyResourceDescriptionInterface>((res, rej) => {
|
||||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
return res(playerResourceDescriptor);
|
return res(playerResourceDescriptor);
|
||||||
}
|
}
|
||||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||||
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
const errorCallback = (file: { src: string }) => {
|
||||||
|
if (file.src !== playerResourceDescriptor.img) return;
|
||||||
|
console.error("failed loading player ressource: ", playerResourceDescriptor);
|
||||||
|
rej(playerResourceDescriptor);
|
||||||
|
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
|
||||||
|
loadPlugin.off("loaderror", errorCallback);
|
||||||
|
};
|
||||||
|
const successCallback = () => {
|
||||||
|
loadPlugin.off("loaderror", errorCallback);
|
||||||
|
res(playerResourceDescriptor);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
|
||||||
|
loadPlugin.on("loaderror", errorCallback);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import type {PlayerInterface} from "./PlayerInterface";
|
||||||
|
|
||||||
export interface AddPlayerInterface {
|
export interface AddPlayerInterface extends PlayerInterface {
|
||||||
userId: number;
|
|
||||||
name: string;
|
|
||||||
characterLayers: BodyResourceDescriptionInterface[];
|
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
visitCardUrl: string|null;
|
|
||||||
companion: string|null;
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import {ResizableScene} from "../Login/ResizableScene";
|
import { ResizableScene } from "../Login/ResizableScene";
|
||||||
import GameObject = Phaser.GameObjects.GameObject;
|
import GameObject = Phaser.GameObjects.GameObject;
|
||||||
import Events = Phaser.Scenes.Events;
|
import Events = Phaser.Scenes.Events;
|
||||||
import AnimationEvents = Phaser.Animations.Events;
|
import AnimationEvents = Phaser.Animations.Events;
|
||||||
import StructEvents = Phaser.Structs.Events;
|
import StructEvents = Phaser.Structs.Events;
|
||||||
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A scene that can track its dirty/pristine state.
|
* A scene that can track its dirty/pristine state.
|
||||||
*/
|
*/
|
||||||
export abstract class DirtyScene extends ResizableScene {
|
export abstract class DirtyScene extends ResizableScene {
|
||||||
private isAlreadyTracking: boolean = false;
|
private isAlreadyTracking: boolean = false;
|
||||||
protected dirty:boolean = true;
|
protected dirty: boolean = true;
|
||||||
private objectListChanged:boolean = true;
|
private objectListChanged: boolean = true;
|
||||||
private physicsEnabled: boolean = false;
|
private physicsEnabled: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +59,6 @@ export abstract class DirtyScene extends ResizableScene {
|
|||||||
this.physicsEnabled = false;
|
this.physicsEnabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private trackAnimation(): void {
|
private trackAnimation(): void {
|
||||||
@ -71,7 +70,7 @@ export abstract class DirtyScene extends ResizableScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public markDirty(): void {
|
public markDirty(): void {
|
||||||
this.events.once(Phaser.Scenes.Events.POST_UPDATE, () => this.dirty = true);
|
this.events.once(Phaser.Scenes.Events.POST_UPDATE, () => (this.dirty = true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public onResize(): void {
|
public onResize(): void {
|
||||||
|
@ -1,26 +1,24 @@
|
|||||||
import {GameScene} from "./GameScene";
|
import { GameScene } from "./GameScene";
|
||||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import type {Room} from "../../Connexion/Room";
|
import type { Room } from "../../Connexion/Room";
|
||||||
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
|
import { MenuScene, MenuSceneName } from "../Menu/MenuScene";
|
||||||
import {LoginSceneName} from "../Login/LoginScene";
|
import { LoginSceneName } from "../Login/LoginScene";
|
||||||
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
|
import { SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||||
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
|
import { EnableCameraSceneName } from "../Login/EnableCameraScene";
|
||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import {get} from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
|
import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
||||||
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
|
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class should be responsible for any scene starting/stopping
|
* This class should be responsible for any scene starting/stopping
|
||||||
*/
|
*/
|
||||||
export class GameManager {
|
export class GameManager {
|
||||||
private playerName: string|null;
|
private playerName: string | null;
|
||||||
private characterLayers: string[]|null;
|
private characterLayers: string[] | null;
|
||||||
private companion: string|null;
|
private companion: string | null;
|
||||||
private startRoom!:Room;
|
private startRoom!: Room;
|
||||||
currentGameSceneName: string|null = null;
|
currentGameSceneName: string | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.playerName = localUserStore.getName();
|
this.playerName = localUserStore.getName();
|
||||||
@ -30,7 +28,7 @@ export class GameManager {
|
|||||||
|
|
||||||
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
|
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
|
||||||
this.startRoom = await connectionManager.initGameConnexion();
|
this.startRoom = await connectionManager.initGameConnexion();
|
||||||
await this.loadMap(this.startRoom, scenePlugin);
|
this.loadMap(this.startRoom, scenePlugin);
|
||||||
|
|
||||||
if (!this.playerName) {
|
if (!this.playerName) {
|
||||||
return LoginSceneName;
|
return LoginSceneName;
|
||||||
@ -51,43 +49,44 @@ export class GameManager {
|
|||||||
localUserStore.setCharacterLayers(layers);
|
localUserStore.setCharacterLayers(layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlayerName(): string|null {
|
getPlayerName(): string | null {
|
||||||
return this.playerName;
|
return this.playerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCharacterLayers(): string[] {
|
getCharacterLayers(): string[] {
|
||||||
if (!this.characterLayers) {
|
if (!this.characterLayers) {
|
||||||
throw 'characterLayers are not set';
|
throw "characterLayers are not set";
|
||||||
}
|
}
|
||||||
return this.characterLayers;
|
return this.characterLayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCompanion(companion: string | null): void {
|
||||||
setCompanion(companion: string|null): void {
|
|
||||||
this.companion = companion;
|
this.companion = companion;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCompanion(): string|null {
|
getCompanion(): string | null {
|
||||||
return this.companion;
|
return this.companion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
public loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin) {
|
||||||
const roomID = room.id;
|
const roomID = room.key;
|
||||||
const mapDetail = await room.getMapDetail();
|
|
||||||
|
|
||||||
const gameIndex = scenePlugin.getIndex(roomID);
|
const gameIndex = scenePlugin.getIndex(roomID);
|
||||||
if(gameIndex === -1){
|
if (gameIndex === -1) {
|
||||||
const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl);
|
const game: Phaser.Scene = new GameScene(room, room.mapUrl);
|
||||||
scenePlugin.add(roomID, game, false);
|
scenePlugin.add(roomID, game, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
|
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
|
||||||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
console.log("starting " + (this.currentGameSceneName || this.startRoom.key));
|
||||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
scenePlugin.start(this.currentGameSceneName || this.startRoom.key);
|
||||||
scenePlugin.launch(MenuSceneName);
|
scenePlugin.launch(MenuSceneName);
|
||||||
|
|
||||||
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
|
if (
|
||||||
|
!localUserStore.getHelpCameraSettingsShown() &&
|
||||||
|
(!get(requestedMicrophoneState) || !get(requestedCameraState))
|
||||||
|
) {
|
||||||
helpCameraSettingsVisibleStore.set(true);
|
helpCameraSettingsVisibleStore.set(true);
|
||||||
localUserStore.setHelpCameraSettingsShown();
|
localUserStore.setHelpCameraSettingsShown();
|
||||||
}
|
}
|
||||||
@ -104,7 +103,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(scene: Phaser.Scene, targetSceneName: string, sceneClass: Phaser.Scene): void {
|
leaveGame(scene: Phaser.Scene, targetSceneName: string, sceneClass: Phaser.Scene): void {
|
||||||
if (this.currentGameSceneName === null) throw 'No current scene id set!';
|
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
||||||
const gameScene: GameScene = scene.scene.get(this.currentGameSceneName) as GameScene;
|
const gameScene: GameScene = scene.scene.get(this.currentGameSceneName) as GameScene;
|
||||||
gameScene.cleanupClosingScene();
|
gameScene.cleanupClosingScene();
|
||||||
scene.scene.stop(this.currentGameSceneName);
|
scene.scene.stop(this.currentGameSceneName);
|
||||||
@ -123,13 +122,13 @@ export class GameManager {
|
|||||||
scene.scene.start(this.currentGameSceneName);
|
scene.scene.start(this.currentGameSceneName);
|
||||||
scene.scene.wake(MenuSceneName);
|
scene.scene.wake(MenuSceneName);
|
||||||
} else {
|
} else {
|
||||||
scene.scene.run(fallbackSceneName)
|
scene.scene.run(fallbackSceneName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentGameScene(scene: Phaser.Scene): GameScene {
|
public getCurrentGameScene(scene: Phaser.Scene): GameScene {
|
||||||
if (this.currentGameSceneName === null) throw 'No current scene id set!';
|
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
||||||
return scene.scene.get(this.currentGameSceneName) as GameScene
|
return scene.scene.get(this.currentGameSceneName) as GameScene;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import type {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty} from "../Map/ITiledMap";
|
import type { ITiledMap, ITiledMapLayer, ITiledMapLayerProperty } from "../Map/ITiledMap";
|
||||||
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
||||||
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
||||||
|
|
||||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
export type PropertyChangeCallback = (
|
||||||
|
newValue: string | number | boolean | undefined,
|
||||||
|
oldValue: string | number | boolean | undefined,
|
||||||
|
allProps: Map<string, string | boolean | number>
|
||||||
|
) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around a ITiledMap interface to provide additional capabilities.
|
* A wrapper around a ITiledMap interface to provide additional capabilities.
|
||||||
@ -13,39 +17,56 @@ export class GameMap {
|
|||||||
private key: number | undefined;
|
private key: number | undefined;
|
||||||
private lastProperties = new Map<string, string | boolean | number>();
|
private lastProperties = new Map<string, string | boolean | number>();
|
||||||
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
|
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
|
||||||
|
private tileNameMap = new Map<string, number>();
|
||||||
|
|
||||||
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapLayerProperty> } = {}
|
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapLayerProperty> } = {};
|
||||||
public readonly flatLayers: ITiledMapLayer[];
|
public readonly flatLayers: ITiledMapLayer[];
|
||||||
public readonly phaserLayers: TilemapLayer[] = [];
|
public readonly phaserLayers: TilemapLayer[] = [];
|
||||||
|
|
||||||
public exitUrls: Array<string> = []
|
public exitUrls: Array<string> = [];
|
||||||
|
|
||||||
public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array<Phaser.Tilemaps.Tileset>) {
|
public hasStartTile = false;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private map: ITiledMap,
|
||||||
|
phaserMap: Phaser.Tilemaps.Tilemap,
|
||||||
|
terrains: Array<Phaser.Tilemaps.Tileset>
|
||||||
|
) {
|
||||||
this.flatLayers = flattenGroupLayersMap(map);
|
this.flatLayers = flattenGroupLayersMap(map);
|
||||||
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, 0, 0).setDepth(depth));
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
||||||
depth = DEPTH_OVERLAY_INDEX;
|
depth = DEPTH_OVERLAY_INDEX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const tileset of map.tilesets) {
|
for (const tileset of map.tilesets) {
|
||||||
tileset?.tiles?.forEach(tile => {
|
tileset?.tiles?.forEach((tile) => {
|
||||||
if (tile.properties) {
|
if (tile.properties) {
|
||||||
this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties
|
this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties;
|
||||||
tile.properties.forEach(prop => {
|
tile.properties.forEach((prop) => {
|
||||||
|
if (prop.name == "name" && typeof prop.value == "string") {
|
||||||
|
this.tileNameMap.set(prop.value, tileset.firstgid + tile.id);
|
||||||
|
}
|
||||||
if (prop.name == "exitUrl" && typeof prop.value == "string") {
|
if (prop.name == "exitUrl" && typeof prop.value == "string") {
|
||||||
this.exitUrls.push(prop.value);
|
this.exitUrls.push(prop.value);
|
||||||
|
} else if (prop.name == "start") {
|
||||||
|
this.hasStartTile = true;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPropertiesForIndex(index: number): Array<ITiledMapLayerProperty> {
|
||||||
|
if (this.tileSetPropertyMap[index]) {
|
||||||
|
return this.tileSetPropertyMap[index];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the position of the current player (in pixels)
|
* Sets the position of the current player (in pixels)
|
||||||
@ -89,7 +110,7 @@ export class GameMap {
|
|||||||
const properties = new Map<string, string | boolean | number>();
|
const properties = new Map<string, string | boolean | number>();
|
||||||
|
|
||||||
for (const layer of this.flatLayers) {
|
for (const layer of this.flatLayers) {
|
||||||
if (layer.type !== 'tilelayer') {
|
if (layer.type !== "tilelayer") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +120,7 @@ export class GameMap {
|
|||||||
if (tiles[key] == 0) {
|
if (tiles[key] == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tileIndex = tiles[key]
|
tileIndex = tiles[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is a tile in this layer, let's embed the properties
|
// There is a tile in this layer, let's embed the properties
|
||||||
@ -113,24 +134,36 @@ export class GameMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tileIndex) {
|
if (tileIndex) {
|
||||||
this.tileSetPropertyMap[tileIndex]?.forEach(property => {
|
this.tileSetPropertyMap[tileIndex]?.forEach((property) => {
|
||||||
if (property.value) {
|
if (property.value) {
|
||||||
properties.set(property.name, property.value)
|
properties.set(property.name, property.value);
|
||||||
} else if (properties.has(property.name)) {
|
} else if (properties.has(property.name)) {
|
||||||
properties.delete(property.name)
|
properties.delete(property.name);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMap(): ITiledMap{
|
public getMap(): ITiledMap {
|
||||||
return this.map;
|
return this.map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) {
|
private getTileProperty(index: number): Array<ITiledMapLayerProperty> {
|
||||||
|
if (this.tileSetPropertyMap[index]) {
|
||||||
|
return this.tileSetPropertyMap[index];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private trigger(
|
||||||
|
propName: string,
|
||||||
|
oldValue: string | number | boolean | undefined,
|
||||||
|
newValue: string | number | boolean | undefined,
|
||||||
|
allProps: Map<string, string | boolean | number>
|
||||||
|
) {
|
||||||
const callbacksArray = this.callbacks.get(propName);
|
const callbacksArray = this.callbacks.get(propName);
|
||||||
if (callbacksArray !== undefined) {
|
if (callbacksArray !== undefined) {
|
||||||
for (const callback of callbacksArray) {
|
for (const callback of callbacksArray) {
|
||||||
@ -159,10 +192,65 @@ export class GameMap {
|
|||||||
return this.phaserLayers.find((layer) => layer.layer.name === layerName);
|
return this.phaserLayers.find((layer) => layer.layer.name === layerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addTerrain(terrain : Phaser.Tilemaps.Tileset): void {
|
public findPhaserLayers(groupName: string): TilemapLayer[] {
|
||||||
|
return this.phaserLayers.filter((l) => l.layer.name.includes(groupName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTerrain(terrain: Phaser.Tilemaps.Tileset): void {
|
||||||
for (const phaserLayer of this.phaserLayers) {
|
for (const phaserLayer of this.phaserLayers) {
|
||||||
phaserLayer.tileset.push(terrain);
|
phaserLayer.tileset.push(terrain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void {
|
||||||
|
const fLayer = this.findLayer(layer);
|
||||||
|
if (fLayer == undefined) {
|
||||||
|
console.error("The layer '" + layer + "' that you want to change doesn't exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fLayer.type !== "tilelayer") {
|
||||||
|
console.error(
|
||||||
|
"The layer '" +
|
||||||
|
layer +
|
||||||
|
"' that you want to change is not a tilelayer. Tile can only be put in tilelayer."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof fLayer.data === "string") {
|
||||||
|
console.error("Data of the layer '" + layer + "' that you want to change is only readable.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fLayer.data[x + y * fLayer.width] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public putTile(tile: string | number | null, x: number, y: number, layer: string): void {
|
||||||
|
const phaserLayer = this.findPhaserLayer(layer);
|
||||||
|
if (phaserLayer) {
|
||||||
|
if (tile === null) {
|
||||||
|
phaserLayer.putTileAt(-1, x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tileIndex = this.getIndexForTileType(tile);
|
||||||
|
if (tileIndex !== undefined) {
|
||||||
|
this.putTileInFlatLayer(tileIndex, x, y, layer);
|
||||||
|
const phaserTile = phaserLayer.putTileAt(tileIndex, x, y);
|
||||||
|
for (const property of this.getTileProperty(tileIndex)) {
|
||||||
|
if (property.name === "collides" && property.value) {
|
||||||
|
phaserTile.setCollision(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("The tile '" + tile + "' that you want to place doesn't exist.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("The layer '" + layer + "' does not exist (or is not a tilelaye).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIndexForTileType(tile: string | number): number | undefined {
|
||||||
|
if (typeof tile == "number") {
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
return this.tileNameMap.get(tile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
11
front/src/Phaser/Game/PlayerInterface.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
|
|
||||||
|
export interface PlayerInterface {
|
||||||
|
userId: number;
|
||||||
|
name: string;
|
||||||
|
characterLayers: BodyResourceDescriptionInterface[];
|
||||||
|
visitCardUrl: string | null;
|
||||||
|
companion: string | null;
|
||||||
|
userUuid: string;
|
||||||
|
color?: string;
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
import { MAX_EXTRAPOLATION_TIME } from "../../Enum/EnvironmentVariable";
|
import { MAX_EXTRAPOLATION_TIME } from "../../Enum/EnvironmentVariable";
|
||||||
import type { PositionInterface } from "../../Connexion/ConnexionModels";
|
import type { PositionInterface } from "../../Connexion/ConnexionModels";
|
||||||
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
|
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||||
|
|
||||||
export class PlayerMovement {
|
export class PlayerMovement {
|
||||||
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasPlayerMovedEvent, private endTick: number) {
|
public constructor(
|
||||||
}
|
private startPosition: PositionInterface,
|
||||||
|
private startTick: number,
|
||||||
|
private endPosition: HasPlayerMovedEvent,
|
||||||
|
private endTick: number
|
||||||
|
) {}
|
||||||
|
|
||||||
public isOutdated(tick: number): boolean {
|
public isOutdated(tick: number): boolean {
|
||||||
//console.log(tick, this.endTick, MAX_EXTRAPOLATION_TIME)
|
//console.log(tick, this.endTick, MAX_EXTRAPOLATION_TIME)
|
||||||
@ -24,14 +28,18 @@ export class PlayerMovement {
|
|||||||
return this.endPosition;
|
return this.endPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = (this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.x;
|
const x =
|
||||||
const y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y;
|
(this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) +
|
||||||
|
this.startPosition.x;
|
||||||
|
const y =
|
||||||
|
(this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) +
|
||||||
|
this.startPosition.y;
|
||||||
//console.log('Computed position ', x, y)
|
//console.log('Computed position ', x, y)
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
direction: this.endPosition.direction,
|
direction: this.endPosition.direction,
|
||||||
moving: true
|
moving: true,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* This class is in charge of computing the position of all players.
|
* This class is in charge of computing the position of all players.
|
||||||
* Player movement is delayed by 200ms so position depends on ticks.
|
* Player movement is delayed by 200ms so position depends on ticks.
|
||||||
*/
|
*/
|
||||||
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
|
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||||
import type { PlayerMovement } from "./PlayerMovement";
|
import type { PlayerMovement } from "./PlayerMovement";
|
||||||
|
|
||||||
export class PlayersPositionInterpolator {
|
export class PlayersPositionInterpolator {
|
||||||
@ -24,7 +24,7 @@ export class PlayersPositionInterpolator {
|
|||||||
this.playerMovements.delete(userId);
|
this.playerMovements.delete(userId);
|
||||||
}
|
}
|
||||||
//console.log("moving")
|
//console.log("moving")
|
||||||
positions.set(userId, playerMovement.getPosition(tick))
|
positions.set(userId, playerMovement.getPosition(tick));
|
||||||
});
|
});
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
127
front/src/Phaser/Game/StartPositionCalculator.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import type { PositionInterface } from "../../Connexion/ConnexionModels";
|
||||||
|
import type { ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapTileLayer } from "../Map/ITiledMap";
|
||||||
|
import type { GameMap } from "./GameMap";
|
||||||
|
|
||||||
|
const defaultStartLayerName = "start";
|
||||||
|
|
||||||
|
export class StartPositionCalculator {
|
||||||
|
public startPosition!: PositionInterface;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly gameMap: GameMap,
|
||||||
|
private readonly mapFile: ITiledMap,
|
||||||
|
private readonly initPosition: PositionInterface | null,
|
||||||
|
public readonly startLayerName: string | null
|
||||||
|
) {
|
||||||
|
this.initStartXAndStartY();
|
||||||
|
}
|
||||||
|
private initStartXAndStartY() {
|
||||||
|
// If there is an init position passed
|
||||||
|
if (this.initPosition !== null) {
|
||||||
|
this.startPosition = this.initPosition;
|
||||||
|
} else {
|
||||||
|
// Now, let's find the start layer
|
||||||
|
if (this.startLayerName) {
|
||||||
|
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
|
||||||
|
}
|
||||||
|
if (this.startPosition === undefined) {
|
||||||
|
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
||||||
|
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||||
|
if (this.startPosition === undefined) {
|
||||||
|
console.warn(
|
||||||
|
'This map is missing a layer named "start" that contains the available default start positions.'
|
||||||
|
);
|
||||||
|
// Let's start in the middle of the map
|
||||||
|
this.startPosition = {
|
||||||
|
x: this.mapFile.width * 16,
|
||||||
|
y: this.mapFile.height * 16,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param selectedLayer this is always the layer that is selected with the hash in the url
|
||||||
|
* @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} didnt yield any start points
|
||||||
|
*/
|
||||||
|
public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) {
|
||||||
|
if (!selectedOrDefaultLayer) {
|
||||||
|
selectedOrDefaultLayer = defaultStartLayerName;
|
||||||
|
}
|
||||||
|
for (const layer of this.gameMap.flatLayers) {
|
||||||
|
if (
|
||||||
|
(selectedOrDefaultLayer === layer.name || layer.name.endsWith("/" + selectedOrDefaultLayer)) &&
|
||||||
|
layer.type === "tilelayer" &&
|
||||||
|
(selectedOrDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))
|
||||||
|
) {
|
||||||
|
const startPosition = this.startUser(layer, selectedLayer);
|
||||||
|
this.startPosition = {
|
||||||
|
x: startPosition.x + this.mapFile.tilewidth / 2,
|
||||||
|
y: startPosition.y + this.mapFile.tileheight / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isStartLayer(layer: ITiledMapLayer): boolean {
|
||||||
|
return this.getProperty(layer, "startLayer") == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param selectedLayer this is always the layer that is selected with the hash in the url
|
||||||
|
* @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} didnt yield any start points
|
||||||
|
*/
|
||||||
|
private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface {
|
||||||
|
const tiles = selectedOrDefaultLayer.data;
|
||||||
|
if (typeof tiles === "string") {
|
||||||
|
throw new Error("The content of a JSON map must be filled as a JSON array, not as a string");
|
||||||
|
}
|
||||||
|
const possibleStartPositions: PositionInterface[] = [];
|
||||||
|
tiles.forEach((objectKey: number, key: number) => {
|
||||||
|
if (objectKey === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const y = Math.floor(key / selectedOrDefaultLayer.width);
|
||||||
|
const x = key % selectedOrDefaultLayer.width;
|
||||||
|
|
||||||
|
if (selectedLayer && this.gameMap.hasStartTile) {
|
||||||
|
const properties = this.gameMap.getPropertiesForIndex(objectKey);
|
||||||
|
if (
|
||||||
|
!properties.length ||
|
||||||
|
!properties.some((property) => property.name == "start" && property.value == selectedLayer)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth });
|
||||||
|
});
|
||||||
|
// Get a value at random amongst allowed values
|
||||||
|
if (possibleStartPositions.length === 0) {
|
||||||
|
console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.');
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Choose one of the available start positions at random amongst the list of available start positions.
|
||||||
|
return possibleStartPositions[Math.floor(Math.random() * possibleStartPositions.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined {
|
||||||
|
const properties: ITiledMapLayerProperty[] | undefined = layer.properties;
|
||||||
|
if (!properties) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const obj = properties.find(
|
||||||
|
(property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase()
|
||||||
|
);
|
||||||
|
if (obj === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return obj.value;
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,19 @@
|
|||||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||||
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager";
|
import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import {gameManager} from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import {addLoader} from "../Components/Loader";
|
import { addLoader } from "../Components/Loader";
|
||||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||||
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
|
import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import {isMobile} from "../../Enum/EnvironmentVariable";
|
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||||
import {CustomizedCharacter} from "../Entity/CustomizedCharacter";
|
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
export const CustomizeSceneName = "CustomizeScene";
|
export const CustomizeSceneName = "CustomizeScene";
|
||||||
|
|
||||||
@ -21,7 +22,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
|
|
||||||
private selectedLayers: number[] = [0];
|
private selectedLayers: number[] = [0];
|
||||||
private containersRow: CustomizedCharacter[][] = [];
|
private containersRow: CustomizedCharacter[][] = [];
|
||||||
public activeRow:number = 0;
|
|
||||||
private layers: BodyResourceDescriptionInterface[][] = [];
|
private layers: BodyResourceDescriptionInterface[][] = [];
|
||||||
|
|
||||||
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
||||||
@ -31,16 +31,19 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
key: CustomizeSceneName
|
key: CustomizeSceneName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
|
|
||||||
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
|
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
|
||||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||||
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
|
if (
|
||||||
throw 'Texture level is null';
|
bodyResourceDescription.level == undefined ||
|
||||||
|
bodyResourceDescription.level < 0 ||
|
||||||
|
bodyResourceDescription.level > 5
|
||||||
|
) {
|
||||||
|
throw "Texture level is null";
|
||||||
}
|
}
|
||||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||||
});
|
});
|
||||||
@ -50,14 +53,13 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
this.layers = loadAllLayers(this.load);
|
this.layers = loadAllLayers(this.load);
|
||||||
this.lazyloadingAttempt = false;
|
this.lazyloadingAttempt = false;
|
||||||
|
|
||||||
|
|
||||||
//this function must stay at the end of preload function
|
//this function must stay at the end of preload function
|
||||||
addLoader(this);
|
addLoader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
customCharacterSceneVisibleStore.set(true);
|
customCharacterSceneVisibleStore.set(true);
|
||||||
this.events.addListener('wake', () => {
|
this.events.addListener("wake", () => {
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
||||||
customCharacterSceneVisibleStore.set(true);
|
customCharacterSceneVisibleStore.set(true);
|
||||||
@ -66,8 +68,13 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
||||||
|
|
||||||
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33)
|
this.Rectangle = this.add.rectangle(
|
||||||
this.Rectangle.setStrokeStyle(2, 0xFFFFFF);
|
this.cameras.main.worldView.x + this.cameras.main.width / 2,
|
||||||
|
this.cameras.main.worldView.y + this.cameras.main.height / 3,
|
||||||
|
32,
|
||||||
|
33
|
||||||
|
);
|
||||||
|
this.Rectangle.setStrokeStyle(2, 0xffffff);
|
||||||
this.add.existing(this.Rectangle);
|
this.add.existing(this.Rectangle);
|
||||||
|
|
||||||
this.createCustomizeLayer(0, 0, 0);
|
this.createCustomizeLayer(0, 0, 0);
|
||||||
@ -78,24 +85,24 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
this.createCustomizeLayer(0, 0, 5);
|
this.createCustomizeLayer(0, 0, 5);
|
||||||
|
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
this.input.keyboard.on('keyup-ENTER', () => {
|
this.input.keyboard.on("keyup-ENTER", () => {
|
||||||
this.nextSceneToCamera();
|
this.nextSceneToCamera();
|
||||||
});
|
});
|
||||||
this.input.keyboard.on('keyup-BACKSPACE', () => {
|
this.input.keyboard.on("keyup-BACKSPACE", () => {
|
||||||
this.backToPreviousScene();
|
this.backToPreviousScene();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
|
// Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
|
||||||
// because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
|
// because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
|
||||||
// explain, the list of sprites managed by the update list become immense
|
// explain, the list of sprites managed by the update list become immense
|
||||||
this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1);
|
this.input.keyboard.on("keyup-RIGHT", () => (this.moveHorizontally = 1));
|
||||||
this.input.keyboard.on('keyup-LEFT', () => this.moveHorizontally = -1);
|
this.input.keyboard.on("keyup-LEFT", () => (this.moveHorizontally = -1));
|
||||||
this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1);
|
this.input.keyboard.on("keyup-DOWN", () => (this.moveVertically = 1));
|
||||||
this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1);
|
this.input.keyboard.on("keyup-UP", () => (this.moveVertically = -1));
|
||||||
|
|
||||||
const customCursorPosition = localUserStore.getCustomCursorPosition();
|
const customCursorPosition = localUserStore.getCustomCursorPosition();
|
||||||
if (customCursorPosition) {
|
if (customCursorPosition) {
|
||||||
this.activeRow = customCursorPosition.activeRow;
|
activeRowStore.set(customCursorPosition.activeRow);
|
||||||
this.selectedLayers = customCursorPosition.selectedLayers;
|
this.selectedLayers = customCursorPosition.selectedLayers;
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
this.updateSelectedLayer();
|
this.updateSelectedLayer();
|
||||||
@ -113,31 +120,30 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private doMoveCursorHorizontally(index: number): void {
|
private doMoveCursorHorizontally(index: number): void {
|
||||||
this.selectedLayers[this.activeRow] += index;
|
this.selectedLayers[get(activeRowStore)] += index;
|
||||||
if (this.selectedLayers[this.activeRow] < 0) {
|
if (this.selectedLayers[get(activeRowStore)] < 0) {
|
||||||
this.selectedLayers[this.activeRow] = 0
|
this.selectedLayers[get(activeRowStore)] = 0;
|
||||||
} else if(this.selectedLayers[this.activeRow] > this.layers[this.activeRow].length - 1) {
|
} else if (this.selectedLayers[get(activeRowStore)] > this.layers[get(activeRowStore)].length - 1) {
|
||||||
this.selectedLayers[this.activeRow] = this.layers[this.activeRow].length - 1
|
this.selectedLayers[get(activeRowStore)] = this.layers[get(activeRowStore)].length - 1;
|
||||||
}
|
}
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
this.updateSelectedLayer();
|
this.updateSelectedLayer();
|
||||||
this.saveInLocalStorage();
|
this.saveInLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private doMoveCursorVertically(index:number): void {
|
private doMoveCursorVertically(index: number): void {
|
||||||
|
activeRowStore.set(get(activeRowStore) + index);
|
||||||
this.activeRow += index;
|
if (get(activeRowStore) < 0) {
|
||||||
if (this.activeRow < 0) {
|
activeRowStore.set(0);
|
||||||
this.activeRow = 0
|
} else if (get(activeRowStore) > this.layers.length - 1) {
|
||||||
} else if (this.activeRow > this.layers.length - 1) {
|
activeRowStore.set(this.layers.length - 1);
|
||||||
this.activeRow = this.layers.length - 1
|
|
||||||
}
|
}
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
this.saveInLocalStorage();
|
this.saveInLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveInLocalStorage() {
|
private saveInLocalStorage() {
|
||||||
localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers);
|
localUserStore.setCustomCursorPosition(get(activeRowStore), this.selectedLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,7 +179,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
* @param selectedItem, The number of the item select (0 for black body...)
|
* @param selectedItem, The number of the item select (0 for black body...)
|
||||||
*/
|
*/
|
||||||
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
|
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
|
||||||
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem));
|
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber, selectedItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
|
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
|
||||||
@ -188,7 +194,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
children.push(this.layers[j][layer].name);
|
children.push(this.layers[j][layer].name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,17 +208,16 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
const screenHeight = this.game.renderer.height;
|
const screenHeight = this.game.renderer.height;
|
||||||
for (let i = 0; i < this.containersRow.length; i++) {
|
for (let i = 0; i < this.containersRow.length; i++) {
|
||||||
for (let j = 0; j < this.containersRow[i].length; j++) {
|
for (let j = 0; j < this.containersRow[i].length; j++) {
|
||||||
let selectedX = this.selectedLayers[i];
|
let selectedX = this.selectedLayers[i];
|
||||||
if (selectedX === undefined) {
|
if (selectedX === undefined) {
|
||||||
selectedX = 0;
|
selectedX = 0;
|
||||||
}
|
}
|
||||||
this.containersRow[i][j].x = screenCenterX + (j - selectedX) * 40;
|
this.containersRow[i][j].x = screenCenterX + (j - selectedX) * 40;
|
||||||
this.containersRow[i][j].y = screenCenterY + (i - this.activeRow) * 40;
|
this.containersRow[i][j].y = screenCenterY + (i - get(activeRowStore)) * 40;
|
||||||
const alpha1 = Math.abs(selectedX - j)*47*2/screenWidth;
|
const alpha1 = (Math.abs(selectedX - j) * 47 * 2) / screenWidth;
|
||||||
const alpha2 = Math.abs(this.activeRow - i)*49*2/screenHeight;
|
const alpha2 = (Math.abs(get(activeRowStore) - i) * 49 * 2) / screenHeight;
|
||||||
this.containersRow[i][j].setAlpha((1 -alpha1)*(1 - alpha2));
|
this.containersRow[i][j].setAlpha((1 - alpha1) * (1 - alpha2));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,8 +233,8 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateSelectedLayer() {
|
private updateSelectedLayer() {
|
||||||
for(let i = 0; i < this.containersRow.length; i++){
|
for (let i = 0; i < this.containersRow.length; i++) {
|
||||||
for(let j = 0; j < this.containersRow[i].length; j++){
|
for (let j = 0; j < this.containersRow[i].length; j++) {
|
||||||
const children = this.getContainerChildren(i, j);
|
const children = this.getContainerChildren(i, j);
|
||||||
this.containersRow[i][j].updateSprites(children);
|
this.containersRow[i][j].updateSprites(children);
|
||||||
}
|
}
|
||||||
@ -237,8 +242,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(time: number, delta: number): void {
|
update(time: number, delta: number): void {
|
||||||
|
if (this.lazyloadingAttempt) {
|
||||||
if(this.lazyloadingAttempt){
|
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
this.lazyloadingAttempt = false;
|
this.lazyloadingAttempt = false;
|
||||||
}
|
}
|
||||||
@ -253,38 +257,35 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onResize(): void {
|
||||||
|
|
||||||
|
|
||||||
public onResize(): void {
|
|
||||||
this.moveLayers();
|
this.moveLayers();
|
||||||
|
|
||||||
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||||
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
||||||
}
|
|
||||||
|
|
||||||
public nextSceneToCamera(){
|
|
||||||
const layers: string[] = [];
|
|
||||||
let i = 0;
|
|
||||||
for (const layerItem of this.selectedLayers) {
|
|
||||||
if (layerItem !== undefined) {
|
|
||||||
layers.push(this.layers[i][layerItem].name);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
if (!areCharacterLayersValid(layers)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gameManager.setCharacterLayers(layers);
|
|
||||||
this.scene.sleep(CustomizeSceneName);
|
|
||||||
waScaleManager.restoreZoom();
|
|
||||||
this.events.removeListener('wake');
|
|
||||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
|
||||||
customCharacterSceneVisibleStore.set(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public backToPreviousScene(){
|
public nextSceneToCamera() {
|
||||||
|
const layers: string[] = [];
|
||||||
|
let i = 0;
|
||||||
|
for (const layerItem of this.selectedLayers) {
|
||||||
|
if (layerItem !== undefined) {
|
||||||
|
layers.push(this.layers[i][layerItem].name);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!areCharacterLayersValid(layers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameManager.setCharacterLayers(layers);
|
||||||
|
this.scene.sleep(CustomizeSceneName);
|
||||||
|
waScaleManager.restoreZoom();
|
||||||
|
this.events.removeListener("wake");
|
||||||
|
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||||
|
customCharacterSceneVisibleStore.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public backToPreviousScene() {
|
||||||
this.scene.sleep(CustomizeSceneName);
|
this.scene.sleep(CustomizeSceneName);
|
||||||
waScaleManager.restoreZoom();
|
waScaleManager.restoreZoom();
|
||||||
this.scene.run(SelectCharacterSceneName);
|
this.scene.run(SelectCharacterSceneName);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {gameManager} from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import {Scene} from "phaser";
|
import { Scene } from "phaser";
|
||||||
import {ErrorScene} from "../Reconnecting/ErrorScene";
|
import { ErrorScene } from "../Reconnecting/ErrorScene";
|
||||||
import {WAError} from "../Reconnecting/WAError";
|
import { WAError } from "../Reconnecting/WAError";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
|
|
||||||
export const EntrySceneName = "EntryScene";
|
export const EntrySceneName = "EntryScene";
|
||||||
|
|
||||||
@ -13,26 +13,32 @@ export const EntrySceneName = "EntryScene";
|
|||||||
export class EntryScene extends Scene {
|
export class EntryScene extends Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
key: EntrySceneName
|
key: EntrySceneName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
gameManager
|
||||||
gameManager.init(this.scene).then((nextSceneName) => {
|
.init(this.scene)
|
||||||
// Let's rescale before starting the game
|
.then((nextSceneName) => {
|
||||||
// We can do it at this stage.
|
// Let's rescale before starting the game
|
||||||
waScaleManager.applyNewSize();
|
// We can do it at this stage.
|
||||||
this.scene.start(nextSceneName);
|
waScaleManager.applyNewSize();
|
||||||
}).catch((err) => {
|
this.scene.start(nextSceneName);
|
||||||
if (err.response && err.response.status == 404) {
|
})
|
||||||
ErrorScene.showError(new WAError(
|
.catch((err) => {
|
||||||
'Access link incorrect',
|
if (err.response && err.response.status == 404) {
|
||||||
'Could not find map. Please check your access link.',
|
ErrorScene.showError(
|
||||||
'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'), this.scene);
|
new WAError(
|
||||||
} else {
|
"Access link incorrect",
|
||||||
ErrorScene.showError(err, this.scene);
|
"Could not find map. Please check your access link.",
|
||||||
}
|
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re"
|
||||||
});
|
),
|
||||||
|
this.scene
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ErrorScene.showError(err, this.scene);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import {gameManager} from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||||
import {CustomizeSceneName} from "./CustomizeScene";
|
import { CustomizeSceneName } from "./CustomizeScene";
|
||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
|
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import {addLoader} from "../Components/Loader";
|
import { addLoader } from "../Components/Loader";
|
||||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||||
import {PinchManager} from "../UserInput/PinchManager";
|
import { PinchManager } from "../UserInput/PinchManager";
|
||||||
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import {isMobile} from "../../Enum/EnvironmentVariable";
|
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
//todo: put this constants in a dedicated file
|
//todo: put this constants in a dedicated file
|
||||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||||
|
|
||||||
export class SelectCharacterScene extends AbstractCharacterScene {
|
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||||
protected readonly nbCharactersPerRow = 6;
|
protected readonly nbCharactersPerRow = 6;
|
||||||
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
|
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite | null; // null if we are selecting the "customize" option
|
||||||
protected players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
protected players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||||
protected playerModels!: BodyResourceDescriptionInterface[];
|
protected playerModels!: BodyResourceDescriptionInterface[];
|
||||||
|
|
||||||
@ -38,7 +38,6 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
|
|
||||||
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
|
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
|
||||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||||
this.playerModels.push(bodyResourceDescription);
|
this.playerModels.push(bodyResourceDescription);
|
||||||
@ -54,7 +53,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
|
|
||||||
create() {
|
create() {
|
||||||
selectCharacterSceneVisibleStore.set(true);
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
this.events.addListener('wake', () => {
|
this.events.addListener("wake", () => {
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||||
selectCharacterSceneVisibleStore.set(true);
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
@ -68,26 +67,26 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||||
|
|
||||||
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
||||||
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
|
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xffffff);
|
||||||
this.selectedRectangle.setDepth(2);
|
this.selectedRectangle.setDepth(2);
|
||||||
|
|
||||||
/*create user*/
|
/*create user*/
|
||||||
this.createCurrentPlayer();
|
this.createCurrentPlayer();
|
||||||
|
|
||||||
this.input.keyboard.on('keyup-ENTER', () => {
|
this.input.keyboard.on("keyup-ENTER", () => {
|
||||||
return this.nextSceneToCameraScene();
|
return this.nextSceneToCameraScene();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.input.keyboard.on('keydown-RIGHT', () => {
|
this.input.keyboard.on("keydown-RIGHT", () => {
|
||||||
this.moveToRight();
|
this.moveToRight();
|
||||||
});
|
});
|
||||||
this.input.keyboard.on('keydown-LEFT', () => {
|
this.input.keyboard.on("keydown-LEFT", () => {
|
||||||
this.moveToLeft();
|
this.moveToLeft();
|
||||||
});
|
});
|
||||||
this.input.keyboard.on('keydown-UP', () => {
|
this.input.keyboard.on("keydown-UP", () => {
|
||||||
this.moveToUp();
|
this.moveToUp();
|
||||||
});
|
});
|
||||||
this.input.keyboard.on('keydown-DOWN', () => {
|
this.input.keyboard.on("keydown-DOWN", () => {
|
||||||
this.moveToDown();
|
this.moveToDown();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -96,7 +95,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!this.selectedPlayer){
|
if (!this.selectedPlayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scene.stop(SelectCharacterSceneName);
|
this.scene.stop(SelectCharacterSceneName);
|
||||||
@ -105,7 +104,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||||
this.players = [];
|
this.players = [];
|
||||||
selectCharacterSceneVisibleStore.set(false);
|
selectCharacterSceneVisibleStore.set(false);
|
||||||
this.events.removeListener('wake');
|
this.events.removeListener("wake");
|
||||||
}
|
}
|
||||||
|
|
||||||
public nextSceneToCustomizeScene(): void {
|
public nextSceneToCustomizeScene(): void {
|
||||||
@ -119,11 +118,11 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createCurrentPlayer(): void {
|
createCurrentPlayer(): void {
|
||||||
for (let i = 0; i <this.playerModels.length; i++) {
|
for (let i = 0; i < this.playerModels.length; i++) {
|
||||||
const playerResource = this.playerModels[i];
|
const playerResource = this.playerModels[i];
|
||||||
|
|
||||||
//check already exist texture
|
//check already exist texture
|
||||||
if(this.players.find((c) => c.texture.key === playerResource.name)){
|
if (this.players.find((c) => c.texture.key === playerResource.name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,9 +131,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
this.setUpPlayer(player, i);
|
this.setUpPlayer(player, i);
|
||||||
this.anims.create({
|
this.anims.create({
|
||||||
key: playerResource.name,
|
key: playerResource.name,
|
||||||
frames: this.anims.generateFrameNumbers(playerResource.name, {start: 0, end: 11}),
|
frames: this.anims.generateFrameNumbers(playerResource.name, { start: 0, end: 11 }),
|
||||||
frameRate: 8,
|
frameRate: 8,
|
||||||
repeat: -1
|
repeat: -1,
|
||||||
});
|
});
|
||||||
player.setInteractive().on("pointerdown", () => {
|
player.setInteractive().on("pointerdown", () => {
|
||||||
if (this.pointerClicked) {
|
if (this.pointerClicked) {
|
||||||
@ -153,77 +152,79 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
});
|
});
|
||||||
this.players.push(player);
|
this.players.push(player);
|
||||||
}
|
}
|
||||||
|
if (this.currentSelectUser >= this.players.length) {
|
||||||
|
this.currentSelectUser = 0;
|
||||||
|
}
|
||||||
this.selectedPlayer = this.players[this.currentSelectUser];
|
this.selectedPlayer = this.players[this.currentSelectUser];
|
||||||
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
|
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected moveUser(){
|
protected moveUser() {
|
||||||
for(let i = 0; i < this.players.length; i++){
|
for (let i = 0; i < this.players.length; i++) {
|
||||||
const player = this.players[i];
|
const player = this.players[i];
|
||||||
this.setUpPlayer(player, i);
|
this.setUpPlayer(player, i);
|
||||||
}
|
}
|
||||||
this.updateSelectedPlayer();
|
this.updateSelectedPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveToLeft(){
|
public moveToLeft() {
|
||||||
if(this.currentSelectUser === 0){
|
if (this.currentSelectUser === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.currentSelectUser -= 1;
|
this.currentSelectUser -= 1;
|
||||||
this.moveUser();
|
this.moveUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveToRight(){
|
public moveToRight() {
|
||||||
if(this.currentSelectUser === (this.players.length - 1)){
|
if (this.currentSelectUser === this.players.length - 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.currentSelectUser += 1;
|
this.currentSelectUser += 1;
|
||||||
this.moveUser();
|
this.moveUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected moveToUp(){
|
protected moveToUp() {
|
||||||
if(this.currentSelectUser < this.nbCharactersPerRow){
|
if (this.currentSelectUser < this.nbCharactersPerRow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.currentSelectUser -= this.nbCharactersPerRow;
|
this.currentSelectUser -= this.nbCharactersPerRow;
|
||||||
this.moveUser();
|
this.moveUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected moveToDown(){
|
protected moveToDown() {
|
||||||
if((this.currentSelectUser + this.nbCharactersPerRow) > (this.players.length - 1)){
|
if (this.currentSelectUser + this.nbCharactersPerRow > this.players.length - 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.currentSelectUser += this.nbCharactersPerRow;
|
this.currentSelectUser += this.nbCharactersPerRow;
|
||||||
this.moveUser();
|
this.moveUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected defineSetupPlayer(num: number){
|
protected defineSetupPlayer(num: number) {
|
||||||
const deltaX = 32;
|
const deltaX = 32;
|
||||||
const deltaY = 32;
|
const deltaY = 32;
|
||||||
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
|
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
|
||||||
|
|
||||||
playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (num % this.nbCharactersPerRow)) ); // calcul position on line users
|
playerX = playerX - deltaX * 2.5 + deltaX * (num % this.nbCharactersPerRow); // calcul position on line users
|
||||||
playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(num / this.nbCharactersPerRow) )) ); // calcul position on column users
|
playerY = playerY - deltaY * 2 + deltaY * Math.floor(num / this.nbCharactersPerRow); // calcul position on column users
|
||||||
|
|
||||||
const playerVisible = true;
|
const playerVisible = true;
|
||||||
const playerScale = 1;
|
const playerScale = 1;
|
||||||
const playerOpacity = 1;
|
const playerOpacity = 1;
|
||||||
|
|
||||||
// if selected
|
// if selected
|
||||||
if( num === this.currentSelectUser ){
|
if (num === this.currentSelectUser) {
|
||||||
this.selectedRectangle.setX(playerX);
|
this.selectedRectangle.setX(playerX);
|
||||||
this.selectedRectangle.setY(playerY);
|
this.selectedRectangle.setY(playerY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {playerX, playerY, playerScale, playerOpacity, playerVisible}
|
return { playerX, playerY, playerScale, playerOpacity, playerVisible };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number){
|
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number) {
|
||||||
|
const { playerX, playerY, playerScale, playerOpacity, playerVisible } = this.defineSetupPlayer(num);
|
||||||
const {playerX, playerY, playerScale, playerOpacity, playerVisible} = this.defineSetupPlayer(num);
|
|
||||||
player.setBounce(0.2);
|
player.setBounce(0.2);
|
||||||
player.setCollideWorldBounds(false);
|
player.setCollideWorldBounds(false);
|
||||||
player.setVisible( playerVisible );
|
player.setVisible(playerVisible);
|
||||||
player.setScale(playerScale, playerScale);
|
player.setScale(playerScale, playerScale);
|
||||||
player.setAlpha(playerOpacity);
|
player.setAlpha(playerOpacity);
|
||||||
player.setX(playerX);
|
player.setX(playerX);
|
||||||
@ -234,10 +235,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
* Returns pixel position by on column and row number
|
* Returns pixel position by on column and row number
|
||||||
*/
|
*/
|
||||||
protected getCharacterPosition(): [number, number] {
|
protected getCharacterPosition(): [number, number] {
|
||||||
return [
|
return [this.game.renderer.width / 2, this.game.renderer.height / 2.5];
|
||||||
this.game.renderer.width / 2,
|
|
||||||
this.game.renderer.height / 2.5
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateSelectedPlayer(): void {
|
protected updateSelectedPlayer(): void {
|
||||||
@ -256,7 +254,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
this.pointerClicked = false;
|
this.pointerClicked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.lazyloadingAttempt){
|
if (this.lazyloadingAttempt) {
|
||||||
//re-render players list
|
//re-render players list
|
||||||
this.createCurrentPlayer();
|
this.createCurrentPlayer();
|
||||||
this.moveUser();
|
this.moveUser();
|
||||||
|
@ -36,7 +36,7 @@ export interface ITiledMap {
|
|||||||
export interface ITiledMapLayerProperty {
|
export interface ITiledMapLayerProperty {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
value: string|boolean|number|undefined;
|
value: string | boolean | number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export interface ITiledMapLayerBooleanProperty {
|
/*export interface ITiledMapLayerBooleanProperty {
|
||||||
@ -48,7 +48,7 @@ export interface ITiledMapLayerProperty {
|
|||||||
export type ITiledMapLayer = ITiledMapGroupLayer | ITiledMapObjectLayer | ITiledMapTileLayer;
|
export type ITiledMapLayer = ITiledMapGroupLayer | ITiledMapObjectLayer | ITiledMapTileLayer;
|
||||||
|
|
||||||
export interface ITiledMapGroupLayer {
|
export interface ITiledMapGroupLayer {
|
||||||
id?: number,
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
properties?: ITiledMapLayerProperty[];
|
properties?: ITiledMapLayerProperty[];
|
||||||
@ -64,8 +64,8 @@ export interface ITiledMapGroupLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ITiledMapTileLayer {
|
export interface ITiledMapTileLayer {
|
||||||
id?: number,
|
id?: number;
|
||||||
data: number[]|string;
|
data: number[] | string;
|
||||||
height: number;
|
height: number;
|
||||||
name: string;
|
name: string;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
@ -87,7 +87,7 @@ export interface ITiledMapTileLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ITiledMapObjectLayer {
|
export interface ITiledMapObjectLayer {
|
||||||
id?: number,
|
id?: number;
|
||||||
height: number;
|
height: number;
|
||||||
name: string;
|
name: string;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
@ -117,7 +117,7 @@ export interface ITiledMapObject {
|
|||||||
gid: number;
|
gid: number;
|
||||||
height: number;
|
height: number;
|
||||||
name: string;
|
name: string;
|
||||||
properties: {[key: string]: string};
|
properties: { [key: string]: string };
|
||||||
rotation: number;
|
rotation: number;
|
||||||
type: string;
|
type: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -133,26 +133,26 @@ export interface ITiledMapObject {
|
|||||||
/**
|
/**
|
||||||
* Polygon points
|
* Polygon points
|
||||||
*/
|
*/
|
||||||
polygon: {x: number, y: number}[];
|
polygon: { x: number; y: number }[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polyline points
|
* Polyline points
|
||||||
*/
|
*/
|
||||||
polyline: {x: number, y: number}[];
|
polyline: { x: number; y: number }[];
|
||||||
|
|
||||||
text?: ITiledText
|
text?: ITiledText;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITiledText {
|
export interface ITiledText {
|
||||||
text: string,
|
text: string;
|
||||||
wrap?: boolean,
|
wrap?: boolean;
|
||||||
fontfamily?: string,
|
fontfamily?: string;
|
||||||
pixelsize?: number,
|
pixelsize?: number;
|
||||||
color?: string,
|
color?: string;
|
||||||
underline?: boolean,
|
underline?: boolean;
|
||||||
italic?: boolean,
|
italic?: boolean;
|
||||||
strikeout?: boolean,
|
strikeout?: boolean;
|
||||||
halign?: "center"|"right"|"justify"|"left"
|
halign?: "center" | "right" | "justify" | "left";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITiledTileSet {
|
export interface ITiledTileSet {
|
||||||
@ -163,7 +163,7 @@ export interface ITiledTileSet {
|
|||||||
imagewidth: number;
|
imagewidth: number;
|
||||||
margin: number;
|
margin: number;
|
||||||
name: string;
|
name: string;
|
||||||
properties: {[key: string]: string};
|
properties: { [key: string]: string };
|
||||||
spacing: number;
|
spacing: number;
|
||||||
tilecount: number;
|
tilecount: number;
|
||||||
tileheight: number;
|
tileheight: number;
|
||||||
@ -179,10 +179,10 @@ export interface ITiledTileSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ITile {
|
export interface ITile {
|
||||||
id: number,
|
id: number;
|
||||||
type?: string
|
type?: string;
|
||||||
|
|
||||||
properties?: Array<ITiledMapLayerProperty>
|
properties?: Array<ITiledMapLayerProperty>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITiledMapTerrain {
|
export interface ITiledMapTerrain {
|
||||||
|