Adding support for "readableBy" and "writableBy" in back
This means that we are now loading maps from server side.
This commit is contained in:
parent
3d76f76d3e
commit
dbd5b80636
24 changed files with 768 additions and 132 deletions
|
@ -11,39 +11,54 @@ import {
|
|||
EmoteEventMessage,
|
||||
JoinRoomMessage,
|
||||
SubToPusherRoomMessage,
|
||||
VariableMessage,
|
||||
VariableMessage, VariableWithTagMessage,
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||
import { RoomSocket, ZoneSocket } from "src/RoomManager";
|
||||
import { Admin } from "../Model/Admin";
|
||||
import {adminApi} from "../Services/AdminApi";
|
||||
import {isMapDetailsData, MapDetailsData} from "../Services/AdminApi/MapDetailsData";
|
||||
import {ITiledMap} from "@workadventure/tiled-map-type-guard/dist";
|
||||
import {mapFetcher} from "../Services/MapFetcher";
|
||||
import {VariablesManager} from "../Services/VariablesManager";
|
||||
import {ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
||||
import {LocalUrlError} from "../Services/LocalUrlError";
|
||||
|
||||
export type ConnectCallback = (user: User, group: Group) => void;
|
||||
export type DisconnectCallback = (user: User, group: Group) => void;
|
||||
|
||||
export class GameRoom {
|
||||
private readonly minDistance: number;
|
||||
private readonly groupRadius: number;
|
||||
|
||||
// Users, sorted by ID
|
||||
private readonly users: Map<number, User>;
|
||||
private readonly usersByUuid: Map<string, User>;
|
||||
private readonly groups: Set<Group>;
|
||||
private readonly admins: Set<Admin>;
|
||||
|
||||
private readonly connectCallback: ConnectCallback;
|
||||
private readonly disconnectCallback: DisconnectCallback;
|
||||
private readonly users = new Map<number, User>();
|
||||
private readonly usersByUuid = new Map<string, User>();
|
||||
private readonly groups = new Set<Group>();
|
||||
private readonly admins = new Set<Admin>();
|
||||
|
||||
private itemsState = new Map<number, unknown>();
|
||||
public readonly variables = new Map<string, string>();
|
||||
|
||||
private readonly positionNotifier: PositionNotifier;
|
||||
public readonly roomUrl: string;
|
||||
private versionNumber: number = 1;
|
||||
private nextUserId: number = 1;
|
||||
|
||||
private roomListeners: Set<RoomSocket> = new Set<RoomSocket>();
|
||||
|
||||
constructor(
|
||||
private constructor(
|
||||
public readonly roomUrl: string,
|
||||
private mapUrl: string,
|
||||
private readonly connectCallback: ConnectCallback,
|
||||
private readonly disconnectCallback: DisconnectCallback,
|
||||
private readonly minDistance: number,
|
||||
private readonly groupRadius: number,
|
||||
onEnters: EntersCallback,
|
||||
onMoves: MovesCallback,
|
||||
onLeaves: LeavesCallback,
|
||||
onEmote: EmoteCallback
|
||||
) {
|
||||
// A zone is 10 sprites wide.
|
||||
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves, onEmote);
|
||||
}
|
||||
|
||||
public static async create(
|
||||
roomUrl: string,
|
||||
connectCallback: ConnectCallback,
|
||||
disconnectCallback: DisconnectCallback,
|
||||
|
@ -53,19 +68,12 @@ export class GameRoom {
|
|||
onMoves: MovesCallback,
|
||||
onLeaves: LeavesCallback,
|
||||
onEmote: EmoteCallback
|
||||
) {
|
||||
this.roomUrl = roomUrl;
|
||||
) : Promise<GameRoom> {
|
||||
const mapDetails = await GameRoom.getMapDetails(roomUrl);
|
||||
|
||||
this.users = new Map<number, User>();
|
||||
this.usersByUuid = new Map<string, User>();
|
||||
this.admins = new Set<Admin>();
|
||||
this.groups = new Set<Group>();
|
||||
this.connectCallback = connectCallback;
|
||||
this.disconnectCallback = disconnectCallback;
|
||||
this.minDistance = minDistance;
|
||||
this.groupRadius = groupRadius;
|
||||
// A zone is 10 sprites wide.
|
||||
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves, onEmote);
|
||||
const gameRoom = new GameRoom(roomUrl, mapDetails.mapUrl, connectCallback, disconnectCallback, minDistance, groupRadius, onEnters, onMoves, onLeaves, onEmote);
|
||||
|
||||
return gameRoom;
|
||||
}
|
||||
|
||||
public getGroups(): Group[] {
|
||||
|
@ -299,13 +307,19 @@ export class GameRoom {
|
|||
return this.itemsState;
|
||||
}
|
||||
|
||||
public setVariable(name: string, value: string): void {
|
||||
this.variables.set(name, value);
|
||||
public async setVariable(name: string, value: string, user: User): Promise<void> {
|
||||
// First, let's check if "user" is allowed to modify the variable.
|
||||
const variableManager = await this.getVariableManager();
|
||||
|
||||
const readableBy = variableManager.setVariable(name, value, user);
|
||||
|
||||
// TODO: should we batch those every 100ms?
|
||||
const variableMessage = new VariableMessage();
|
||||
const variableMessage = new VariableWithTagMessage();
|
||||
variableMessage.setName(name);
|
||||
variableMessage.setValue(value);
|
||||
if (readableBy) {
|
||||
variableMessage.setReadableby(readableBy);
|
||||
}
|
||||
|
||||
const subMessage = new SubToPusherRoomMessage();
|
||||
subMessage.setVariablemessage(variableMessage);
|
||||
|
@ -356,4 +370,82 @@ export class GameRoom {
|
|||
public removeRoomListener(socket: RoomSocket) {
|
||||
this.roomListeners.delete(socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the admin server to fetch map details.
|
||||
* If there is no admin server, the map details are generated by analysing the map URL (that must be in the form: /_/instance/map_url)
|
||||
*/
|
||||
private static async getMapDetails(roomUrl: string): Promise<MapDetailsData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
const roomUrlObj = new URL(roomUrl);
|
||||
|
||||
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrlObj.pathname);
|
||||
if (!match) {
|
||||
console.error('Unexpected room URL', roomUrl);
|
||||
throw new Error('Unexpected room URL "' + roomUrl + '"');
|
||||
}
|
||||
|
||||
const mapUrl = roomUrlObj.protocol + "//" + match[1];
|
||||
|
||||
return {
|
||||
mapUrl,
|
||||
policy_type: 1,
|
||||
textures: [],
|
||||
tags: [],
|
||||
}
|
||||
}
|
||||
|
||||
const result = await adminApi.fetchMapDetails(roomUrl);
|
||||
if (!isMapDetailsData(result)) {
|
||||
console.error('Unexpected room details received from server', result);
|
||||
throw new Error('Unexpected room details received from server');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private mapPromise: Promise<ITiledMap>|undefined;
|
||||
|
||||
/**
|
||||
* Returns a promise to the map file.
|
||||
* @throws LocalUrlError if the map we are trying to load is hosted on a local network
|
||||
* @throws Error
|
||||
*/
|
||||
private getMap(): Promise<ITiledMap> {
|
||||
if (!this.mapPromise) {
|
||||
this.mapPromise = mapFetcher.fetchMap(this.mapUrl);
|
||||
}
|
||||
|
||||
return this.mapPromise;
|
||||
}
|
||||
|
||||
private variableManagerPromise: Promise<VariablesManager>|undefined;
|
||||
|
||||
private getVariableManager(): Promise<VariablesManager> {
|
||||
if (!this.variableManagerPromise) {
|
||||
this.variableManagerPromise = new Promise<VariablesManager>((resolve, reject) => {
|
||||
this.getMap().then((map) => {
|
||||
resolve(new VariablesManager(map));
|
||||
}).catch(e => {
|
||||
if (e instanceof LocalUrlError) {
|
||||
// If we are trying to load a local URL, we are probably in test mode.
|
||||
// In this case, let's bypass the server-side checks completely.
|
||||
|
||||
// FIXME: find a way to send a warning to the client side
|
||||
// FIXME: find a way to send a warning to the client side
|
||||
// FIXME: find a way to send a warning to the client side
|
||||
// FIXME: find a way to send a warning to the client side
|
||||
resolve(new VariablesManager(null));
|
||||
} else {
|
||||
reject(e);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
return this.variableManagerPromise;
|
||||
}
|
||||
|
||||
public async getVariablesForTags(tags: string[]): Promise<Map<string, string>> {
|
||||
const variablesManager = await this.getVariableManager();
|
||||
return variablesManager.getVariablesForTags(tags);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue