Merge pull request #1320 from thecodingmachine/loadTileset
Load a json file that describe a tileset in Tile format.
This commit is contained in:
commit
fbe7440e24
@ -1,10 +1,10 @@
|
|||||||
## Version develop
|
## Version develop
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
- New scripting API features :
|
||||||
|
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
||||||
- Rewrote the way authentification works: the auth jwt token can now contains an email instead of an uuid
|
- Rewrote the way authentification works: the auth jwt token can now contains an email instead of an uuid
|
||||||
- Added an OpenId login flow than can be plugged to any OIDC provider.
|
- Added an OpenId login flow than can be plugged to any OIDC provider.
|
||||||
-
|
|
||||||
|
|
||||||
## Version 1.4.10
|
## Version 1.4.10
|
||||||
|
|
||||||
|
@ -163,3 +163,17 @@ WA.room.setTiles([
|
|||||||
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Loading a tileset
|
||||||
|
```
|
||||||
|
WA.room.loadTileset(url: string): Promise<number>
|
||||||
|
```
|
||||||
|
Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset.
|
||||||
|
|
||||||
|
You can create a tileset file in Tile Editor.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
||||||
|
WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]);
|
||||||
|
})
|
||||||
|
```
|
@ -1,5 +1,4 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
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";
|
||||||
@ -10,7 +9,6 @@ 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 { MapDataEvent } from "./MapDataEvent";
|
|
||||||
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";
|
||||||
@ -20,9 +18,11 @@ import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
|||||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||||
import type { SetTilesEvent } from "./SetTilesEvent";
|
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||||
import type { SetVariableEvent } from "./SetVariableEvent";
|
import type { SetVariableEvent } from "./SetVariableEvent";
|
||||||
import {isGameStateEvent} from "./GameStateEvent";
|
import { isGameStateEvent } from "./GameStateEvent";
|
||||||
import {isMapDataEvent} from "./MapDataEvent";
|
import { isMapDataEvent } from "./MapDataEvent";
|
||||||
import {isSetVariableEvent} from "./SetVariableEvent";
|
import { isSetVariableEvent } from "./SetVariableEvent";
|
||||||
|
import type { LoadTilesetEvent } from "./LoadTilesetEvent";
|
||||||
|
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T;
|
data: T;
|
||||||
@ -52,6 +52,7 @@ export type IframeEventMap = {
|
|||||||
playSound: PlaySoundEvent;
|
playSound: PlaySoundEvent;
|
||||||
stopSound: null;
|
stopSound: null;
|
||||||
getState: undefined;
|
getState: undefined;
|
||||||
|
loadTileset: LoadTilesetEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent;
|
registerMenuCommand: MenuItemRegisterEvent;
|
||||||
setTiles: SetTilesEvent;
|
setTiles: SetTilesEvent;
|
||||||
};
|
};
|
||||||
@ -83,7 +84,6 @@ export const isIframeResponseEventWrapper = (event: {
|
|||||||
type?: string;
|
type?: string;
|
||||||
}): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof 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.
|
* 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.
|
||||||
* Types are defined using Type guards that will actually bused to enforce and check types.
|
* Types are defined using Type guards that will actually bused to enforce and check types.
|
||||||
@ -101,22 +101,26 @@ export const iframeQueryMapTypeGuards = {
|
|||||||
query: isSetVariableEvent,
|
query: isSetVariableEvent,
|
||||||
answer: tg.isUndefined,
|
answer: tg.isUndefined,
|
||||||
},
|
},
|
||||||
}
|
loadTileset: {
|
||||||
|
query: isLoadTilesetEvent,
|
||||||
|
answer: tg.isNumber,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
type GuardedType<T> = T extends (x: unknown) => x is (infer T) ? T : never;
|
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||||
type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards;
|
type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards;
|
||||||
type UnknownToVoid<T> = undefined extends T ? void : T;
|
type UnknownToVoid<T> = undefined extends T ? void : T;
|
||||||
|
|
||||||
export type IframeQueryMap = {
|
export type IframeQueryMap = {
|
||||||
[key in keyof IframeQueryMapTypeGuardsType]: {
|
[key in keyof IframeQueryMapTypeGuardsType]: {
|
||||||
query: GuardedType<IframeQueryMapTypeGuardsType[key]['query']>
|
query: GuardedType<IframeQueryMapTypeGuardsType[key]["query"]>;
|
||||||
answer: UnknownToVoid<GuardedType<IframeQueryMapTypeGuardsType[key]['answer']>>
|
answer: UnknownToVoid<GuardedType<IframeQueryMapTypeGuardsType[key]["answer"]>>;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface IframeQuery<T extends keyof IframeQueryMap> {
|
export interface IframeQuery<T extends keyof IframeQueryMap> {
|
||||||
type: T;
|
type: T;
|
||||||
data: IframeQueryMap[T]['query'];
|
data: IframeQueryMap[T]["query"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IframeQueryWrapper<T extends keyof IframeQueryMap> {
|
export interface IframeQueryWrapper<T extends keyof IframeQueryMap> {
|
||||||
@ -126,30 +130,34 @@ export interface IframeQueryWrapper<T extends keyof IframeQueryMap> {
|
|||||||
|
|
||||||
export const isIframeQueryKey = (type: string): type is keyof IframeQueryMap => {
|
export const isIframeQueryKey = (type: string): type is keyof IframeQueryMap => {
|
||||||
return type in iframeQueryMapTypeGuards;
|
return type in iframeQueryMapTypeGuards;
|
||||||
}
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQueryMap> => {
|
export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQueryMap> => {
|
||||||
const type = event.type;
|
const type = event.type;
|
||||||
if (typeof type !== 'string') {
|
if (typeof type !== "string") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!isIframeQueryKey(type)) {
|
if (!isIframeQueryKey(type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return iframeQueryMapTypeGuards[type].query(event.data);
|
return iframeQueryMapTypeGuards[type].query(event.data);
|
||||||
}
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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 const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper<keyof IframeQueryMap> =>
|
||||||
|
typeof event.id === "number" && isIframeQuery(event.query);
|
||||||
|
|
||||||
export interface IframeAnswerEvent<T extends keyof IframeQueryMap> {
|
export interface IframeAnswerEvent<T extends keyof IframeQueryMap> {
|
||||||
id: number;
|
id: number;
|
||||||
type: T;
|
type: T;
|
||||||
data: IframeQueryMap[T]['answer'];
|
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 const isIframeAnswerEvent = (event: {
|
||||||
|
type?: string;
|
||||||
|
id?: number;
|
||||||
|
}): event is IframeAnswerEvent<keyof IframeQueryMap> => typeof event.type === "string" && typeof event.id === "number";
|
||||||
|
|
||||||
export interface IframeErrorAnswerEvent {
|
export interface IframeErrorAnswerEvent {
|
||||||
id: number;
|
id: number;
|
||||||
@ -157,4 +165,9 @@ export interface IframeErrorAnswerEvent {
|
|||||||
error: string;
|
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';
|
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";
|
||||||
|
12
front/src/Api/Events/LoadTilesetEvent.ts
Normal file
12
front/src/Api/Events/LoadTilesetEvent.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isLoadTilesetEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to add a message in the chat.
|
||||||
|
*/
|
||||||
|
export type LoadTilesetEvent = tg.GuardedType<typeof isLoadTilesetEvent>;
|
@ -12,7 +12,8 @@ import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent
|
|||||||
import {
|
import {
|
||||||
IframeErrorAnswerEvent,
|
IframeErrorAnswerEvent,
|
||||||
IframeEvent,
|
IframeEvent,
|
||||||
IframeEventMap, IframeQueryMap,
|
IframeEventMap,
|
||||||
|
IframeQueryMap,
|
||||||
IframeResponseEvent,
|
IframeResponseEvent,
|
||||||
IframeResponseEventMap,
|
IframeResponseEventMap,
|
||||||
isIframeEventWrapper,
|
isIframeEventWrapper,
|
||||||
@ -25,16 +26,16 @@ 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 type { MapDataEvent } from "./Events/MapDataEvent";
|
|
||||||
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";
|
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||||
import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
||||||
|
|
||||||
type AnswererCallback<T extends keyof IframeQueryMap> = (query: IframeQueryMap[T]["query"], source: MessageEventSource | null) => IframeQueryMap[T]["answer"] | PromiseLike<IframeQueryMap[T]["answer"]>;
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
|
query: IframeQueryMap[T]["query"],
|
||||||
|
source: MessageEventSource | null
|
||||||
|
) => IframeQueryMap[T]["answer"] | PromiseLike<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.
|
||||||
@ -112,13 +113,11 @@ class IframeListener {
|
|||||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
private sendPlayerMove: boolean = false;
|
private sendPlayerMove: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
// Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904
|
// Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904
|
||||||
private answerers: {
|
private answerers: {
|
||||||
[str in keyof IframeQueryMap]?: unknown
|
[str in keyof IframeQueryMap]?: unknown;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message",
|
||||||
@ -158,42 +157,56 @@ class IframeListener {
|
|||||||
|
|
||||||
const answerer = this.answerers[query.type] as AnswererCallback<keyof IframeQueryMap> | undefined;
|
const answerer = this.answerers[query.type] as AnswererCallback<keyof IframeQueryMap> | undefined;
|
||||||
if (answerer === undefined) {
|
if (answerer === undefined) {
|
||||||
const errorMsg = 'The iFrame sent a message of type "'+query.type+'" but there is no service configured to answer these messages.';
|
const errorMsg =
|
||||||
|
'The iFrame sent a message of type "' +
|
||||||
|
query.type +
|
||||||
|
'" but there is no service configured to answer these messages.';
|
||||||
console.error(errorMsg);
|
console.error(errorMsg);
|
||||||
iframe.contentWindow?.postMessage({
|
iframe.contentWindow?.postMessage(
|
||||||
id: queryId,
|
{
|
||||||
type: query.type,
|
id: queryId,
|
||||||
error: errorMsg
|
type: query.type,
|
||||||
} as IframeErrorAnswerEvent, '*');
|
error: errorMsg,
|
||||||
|
} as IframeErrorAnswerEvent,
|
||||||
|
"*"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorHandler = (reason: unknown) => {
|
const errorHandler = (reason: unknown) => {
|
||||||
console.error('An error occurred while responding to an iFrame query.', reason);
|
console.error("An error occurred while responding to an iFrame query.", reason);
|
||||||
let reasonMsg: string = '';
|
let reasonMsg: string = "";
|
||||||
if (reason instanceof Error) {
|
if (reason instanceof Error) {
|
||||||
reasonMsg = reason.message;
|
reasonMsg = reason.message;
|
||||||
} else if (typeof reason === 'object') {
|
} else if (typeof reason === "object") {
|
||||||
reasonMsg = reason ? reason.toString() : '';
|
reasonMsg = reason ? reason.toString() : "";
|
||||||
} else if (typeof reason === 'string') {
|
} else if (typeof reason === "string") {
|
||||||
reasonMsg = reason;
|
reasonMsg = reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe?.contentWindow?.postMessage({
|
iframe?.contentWindow?.postMessage(
|
||||||
id: queryId,
|
{
|
||||||
type: query.type,
|
id: queryId,
|
||||||
error: reasonMsg
|
type: query.type,
|
||||||
} as IframeErrorAnswerEvent, '*');
|
error: reasonMsg,
|
||||||
|
} as IframeErrorAnswerEvent,
|
||||||
|
"*"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Promise.resolve(answerer(query.data, message.source)).then((value) => {
|
Promise.resolve(answerer(query.data, message.source))
|
||||||
iframe?.contentWindow?.postMessage({
|
.then((value) => {
|
||||||
id: queryId,
|
iframe?.contentWindow?.postMessage(
|
||||||
type: query.type,
|
{
|
||||||
data: value
|
id: queryId,
|
||||||
}, '*');
|
type: query.type,
|
||||||
}).catch(errorHandler);
|
data: value,
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(errorHandler);
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
errorHandler(reason);
|
errorHandler(reason);
|
||||||
}
|
}
|
||||||
@ -238,7 +251,7 @@ class IframeListener {
|
|||||||
} else if (payload.type === "displayBubble") {
|
} else if (payload.type === "displayBubble") {
|
||||||
this._displayBubbleStream.next();
|
this._displayBubbleStream.next();
|
||||||
} else if (payload.type === "removeBubble") {
|
} else if (payload.type === "removeBubble") {
|
||||||
this._removeBubbleStream.next();
|
this._removeBubbleStream.next();
|
||||||
} else if (payload.type == "onPlayerMove") {
|
} else if (payload.type == "onPlayerMove") {
|
||||||
this.sendPlayerMove = true;
|
this.sendPlayerMove = true;
|
||||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||||
@ -398,8 +411,8 @@ class IframeListener {
|
|||||||
|
|
||||||
setVariable(setVariableEvent: SetVariableEvent) {
|
setVariable(setVariableEvent: SetVariableEvent) {
|
||||||
this.postMessage({
|
this.postMessage({
|
||||||
'type': 'setVariable',
|
type: "setVariable",
|
||||||
'data': setVariableEvent
|
data: setVariableEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,7 +433,7 @@ class IframeListener {
|
|||||||
* @param key The "type" of the query we are answering
|
* @param key The "type" of the query we are answering
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
public registerAnswerer<T extends keyof IframeQueryMap>(key: T, callback: AnswererCallback<T> ): void {
|
public registerAnswerer<T extends keyof IframeQueryMap>(key: T, callback: AnswererCallback<T>): void {
|
||||||
this.answerers[key] = callback;
|
this.answerers[key] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,13 +445,16 @@ class IframeListener {
|
|||||||
// Let's dispatch the message to the other iframes
|
// Let's dispatch the message to the other iframes
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
if (iframe.contentWindow !== source) {
|
if (iframe.contentWindow !== source) {
|
||||||
iframe.contentWindow?.postMessage({
|
iframe.contentWindow?.postMessage(
|
||||||
'type': 'setVariable',
|
{
|
||||||
'data': {
|
type: "setVariable",
|
||||||
key,
|
data: {
|
||||||
value,
|
key,
|
||||||
}
|
value,
|
||||||
}, '*');
|
},
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Observable, Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
||||||
|
|
||||||
@ -105,6 +105,14 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
|||||||
}
|
}
|
||||||
return mapURL;
|
return mapURL;
|
||||||
}
|
}
|
||||||
|
async loadTileset(url: string): Promise<number> {
|
||||||
|
return await queryWorkadventure({
|
||||||
|
type: "loadTileset",
|
||||||
|
data: {
|
||||||
|
url: url,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventureRoomCommands();
|
export default new WorkadventureRoomCommands();
|
||||||
|
@ -64,11 +64,8 @@ class ConnectionManager {
|
|||||||
);
|
);
|
||||||
localUserStore.setAuthToken(authToken);
|
localUserStore.setAuthToken(authToken);
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
room = await Room.createRoom(
|
room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||||
new URL(localUserStore.getLastRoomUrl())
|
|
||||||
);
|
|
||||||
urlManager.pushRoomIdToUrl(room);
|
urlManager.pushRoomIdToUrl(room);
|
||||||
|
|
||||||
} else if (connexionType === GameConnexionTypes.register) {
|
} else if (connexionType === GameConnexionTypes.register) {
|
||||||
//@deprecated
|
//@deprecated
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
|
@ -75,8 +75,6 @@ import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey }
|
|||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { EmoteManager } from "./EmoteManager";
|
import { EmoteManager } from "./EmoteManager";
|
||||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||||
import RenderTexture = Phaser.GameObjects.RenderTexture;
|
|
||||||
import Tilemap = Phaser.Tilemaps.Tilemap;
|
|
||||||
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||||
|
|
||||||
import AnimatedTiles from "phaser-animated-tiles";
|
import AnimatedTiles from "phaser-animated-tiles";
|
||||||
@ -88,6 +86,7 @@ import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStor
|
|||||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||||
import { playersStore } from "../../Stores/PlayersStore";
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||||
|
import Tileset = Phaser.Tilemaps.Tileset;
|
||||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
@ -222,6 +221,9 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
//hook preload scene
|
//hook preload scene
|
||||||
preload(): void {
|
preload(): void {
|
||||||
|
//initialize frame event of scripting API
|
||||||
|
this.listenToIframeEvents();
|
||||||
|
|
||||||
const localUser = localUserStore.getLocalUser();
|
const localUser = localUserStore.getLocalUser();
|
||||||
const textures = localUser?.textures;
|
const textures = localUser?.textures;
|
||||||
if (textures) {
|
if (textures) {
|
||||||
@ -550,7 +552,6 @@ export class GameScene extends DirtyScene {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.triggerOnMapLayerPropertyChange();
|
this.triggerOnMapLayerPropertyChange();
|
||||||
this.listenToIframeEvents();
|
|
||||||
|
|
||||||
if (!this.room.isDisconnected()) {
|
if (!this.room.isDisconnected()) {
|
||||||
this.connect();
|
this.connect();
|
||||||
@ -1084,8 +1085,74 @@ ${escapedMessage}
|
|||||||
for (const eventTile of eventTiles) {
|
for (const eventTile of eventTiles) {
|
||||||
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
||||||
}
|
}
|
||||||
|
this.markDirty();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
|
||||||
|
return this.connectionAnswerPromise.then(() => {
|
||||||
|
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
|
||||||
|
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
|
||||||
|
let newFirstgid = 1;
|
||||||
|
const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1];
|
||||||
|
if (lastTileset) {
|
||||||
|
//If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset
|
||||||
|
newFirstgid = lastTileset.firstgid + lastTileset.tilecount;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.load.on("filecomplete-json-" + eventTileset.url, () => {
|
||||||
|
let jsonTileset = this.cache.json.get(eventTileset.url);
|
||||||
|
const imageUrl = jsonTilesetDir + "/" + jsonTileset.image;
|
||||||
|
this.load.image(imageUrl, imageUrl);
|
||||||
|
this.load.on("filecomplete-image-" + imageUrl, () => {
|
||||||
|
//Add the firstgid of the tileset to the json file
|
||||||
|
jsonTileset = { ...jsonTileset, firstgid: newFirstgid };
|
||||||
|
this.mapFile.tilesets.push(jsonTileset);
|
||||||
|
this.Map.tilesets.push(
|
||||||
|
new Tileset(
|
||||||
|
jsonTileset.name,
|
||||||
|
jsonTileset.firstgid,
|
||||||
|
jsonTileset.tileWidth,
|
||||||
|
jsonTileset.tileHeight,
|
||||||
|
jsonTileset.margin,
|
||||||
|
jsonTileset.spacing,
|
||||||
|
jsonTileset.tiles
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.Terrains.push(
|
||||||
|
this.Map.addTilesetImage(
|
||||||
|
jsonTileset.name,
|
||||||
|
imageUrl,
|
||||||
|
jsonTileset.tilewidth,
|
||||||
|
jsonTileset.tileheight,
|
||||||
|
jsonTileset.margin,
|
||||||
|
jsonTileset.spacing
|
||||||
|
)
|
||||||
|
);
|
||||||
|
//destroy the tilemapayer because they are unique and we need to reuse their key and layerdData
|
||||||
|
for (const layer of this.Map.layers) {
|
||||||
|
layer.tilemapLayer.destroy(false);
|
||||||
|
}
|
||||||
|
//Create a new GameMap with the changed file
|
||||||
|
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
||||||
|
//Destroy the colliders of the old tilemapLayer
|
||||||
|
this.physics.add.world.colliders.destroy();
|
||||||
|
//Create new colliders with the new GameMap
|
||||||
|
this.createCollisionWithPlayer();
|
||||||
|
//Create new trigger with the new GameMap
|
||||||
|
this.triggerOnMapLayerPropertyChange();
|
||||||
|
resolve(newFirstgid);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.load.on("loaderror", () => {
|
||||||
|
console.error("Error while loading " + eventTileset.url + ".");
|
||||||
|
reject(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.load.json(eventTileset.url, eventTileset.url);
|
||||||
|
this.load.start();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setPropertyLayer(
|
private setPropertyLayer(
|
||||||
@ -1153,7 +1220,7 @@ ${escapedMessage}
|
|||||||
let targetRoom: Room;
|
let targetRoom: Room;
|
||||||
try {
|
try {
|
||||||
targetRoom = await Room.createRoom(roomUrl);
|
targetRoom = await Room.createRoom(roomUrl);
|
||||||
} catch (e: unknown) {
|
} catch (e) {
|
||||||
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
||||||
this.mapTransitioning = false;
|
this.mapTransitioning = false;
|
||||||
return;
|
return;
|
||||||
@ -1207,6 +1274,7 @@ ${escapedMessage}
|
|||||||
this.chatVisibilityUnsubscribe();
|
this.chatVisibilityUnsubscribe();
|
||||||
this.biggestAvailableAreaStoreUnsubscribe();
|
this.biggestAvailableAreaStoreUnsubscribe();
|
||||||
iframeListener.unregisterAnswerer("getState");
|
iframeListener.unregisterAnswerer("getState");
|
||||||
|
iframeListener.unregisterAnswerer("loadTileset");
|
||||||
this.sharedVariablesManager?.close();
|
this.sharedVariablesManager?.close();
|
||||||
|
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
@ -1279,7 +1347,7 @@ ${escapedMessage}
|
|||||||
try {
|
try {
|
||||||
const room = await Room.createRoom(exitRoomPath);
|
const room = await Room.createRoom(exitRoomPath);
|
||||||
return gameManager.loadMap(room, this.scene);
|
return gameManager.loadMap(room, this.scene);
|
||||||
} catch (e: unknown) {
|
} catch (e) {
|
||||||
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
159
maps/tests/LoadTileset/LoadTileset.json
Normal file
159
maps/tests/LoadTileset/LoadTileset.json
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":10,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":1,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[33, 34, 33, 34, 34, 34, 35, 37, 38, 39, 41, 42, 41, 42, 42, 42, 43, 45, 46, 47, 33, 34, 60, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 49, 50, 50, 50, 50, 50, 51, 53, 54, 55],
|
||||||
|
"height":10,
|
||||||
|
"id":2,
|
||||||
|
"name":"bottom",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 59, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":10,
|
||||||
|
"id":3,
|
||||||
|
"name":"openwebsite",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal"
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":10,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":4,
|
||||||
|
"nextobjectid":1,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"scriptTileset.js"
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.7.0",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":8,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset_dungeon.png",
|
||||||
|
"imageheight":256,
|
||||||
|
"imagewidth":256,
|
||||||
|
"margin":0,
|
||||||
|
"name":"Dungeon",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":64,
|
||||||
|
"tileheight":32,
|
||||||
|
"tiles":[
|
||||||
|
{
|
||||||
|
"id":36,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":37,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":38,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":44,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":45,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":46,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":52,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":53,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":54,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":"1.6",
|
||||||
|
"width":10
|
||||||
|
}
|
BIN
maps/tests/LoadTileset/Yellow.jpg
Normal file
BIN
maps/tests/LoadTileset/Yellow.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
106
maps/tests/LoadTileset/Yellow.json
Normal file
106
maps/tests/LoadTileset/Yellow.json
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
{ "columns":11,
|
||||||
|
"image":"Yellow.jpg",
|
||||||
|
"imageheight":128,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"Yellow",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":44,
|
||||||
|
"tiledversion":"1.7.0",
|
||||||
|
"tileheight":32,
|
||||||
|
"tiles":[
|
||||||
|
{
|
||||||
|
"id":0,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"name",
|
||||||
|
"type":"string",
|
||||||
|
"value":"Mur"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":2,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":11,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":12,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"name",
|
||||||
|
"type":"string",
|
||||||
|
"value":"sol"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":13,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":22,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":23,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":24,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"collides",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"tileset",
|
||||||
|
"version":"1.6"
|
||||||
|
}
|
6
maps/tests/LoadTileset/scriptTileset.js
Normal file
6
maps/tests/LoadTileset/scriptTileset.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
WA.room.loadTileset("http://maps.workadventure.localhost/tests/LoadTileset/Yellow.json").then((firstgid) => {
|
||||||
|
WA.room.setTiles([
|
||||||
|
{x: 5, y: 5, tile: firstgid + 1, layer: 'bottom'},
|
||||||
|
{x: 5, y: 3, tile: 'sol', layer: 'bottom'}
|
||||||
|
]);
|
||||||
|
});
|
BIN
maps/tests/LoadTileset/tileset_dungeon.png
Normal file
BIN
maps/tests/LoadTileset/tileset_dungeon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
Loading…
Reference in New Issue
Block a user