Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop

This commit is contained in:
Lurkars 2021-07-01 13:39:04 +02:00
commit bba26fdc20
62 changed files with 1995 additions and 867 deletions

View File

@ -199,4 +199,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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"

View File

@ -2,6 +2,7 @@ name: Push @workadventure/iframe-api-typings to NPM
on:
release:
types: [created]
push:
jobs:
build:
runs-on: ubuntu-latest
@ -13,10 +14,6 @@ jobs:
node-version: '14.x'
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
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
working-directory: "front/packages/iframe-api-typings"
@ -47,15 +44,18 @@ jobs:
working-directory: "front"
- name: "Build"
run: yarn run build
run: yarn run build-typings
env:
API_URL: "localhost:8080"
PUSHER_URL: "//localhost:8080"
working-directory: "front"
# 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
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
run: yarn install
working-directory: "front/packages/iframe-api-typings"
@ -65,3 +65,4 @@ jobs:
working-directory: "front/packages/iframe-api-typings"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
if: ${{ github.event_name == 'release' }}

View File

@ -15,6 +15,19 @@
- Use `WA.room.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.ui.registerMenuCommand(): void` to add a custom menu
- Use `WA.room.setTiles(): void` to change an array of tiles
## 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

View File

@ -52,11 +52,11 @@ WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
### 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
```
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:
@ -65,4 +65,3 @@ WA.nav.openCoWebSite('https://www.wikipedia.org/');
// ...
WA.nav.closeCoWebSite();
```

View File

@ -112,3 +112,35 @@ WA.room.getCurrentUser().then((user) => {
}
})
```
### Changing tiles
```
WA.room.setTiles(tiles: TileDescriptor[]): void
```
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
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.
Example :
```javascript
WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
{x: 7, y: 4, tile: 109, layer: 'setTiles'},
{x: 8, y: 4, tile: 109, layer: 'setTiles'},
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
]);
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -60,6 +60,7 @@
"templater": "cross-env ./templater.sh",
"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-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack",
"test": "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",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",

View File

@ -18,7 +18,7 @@ import type { PlaySoundEvent } from "./PlaySoundEvent";
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent';
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
import type { SetTilesEvent } from "./SetTilesEvent";
export interface TypedMessageEvent<T> extends MessageEvent {
data: T
@ -26,7 +26,6 @@ export interface TypedMessageEvent<T> extends MessageEvent {
export type IframeEventMap = {
//getState: GameStateEvent,
// updateTile: UpdateTileEvent
loadPage: LoadPageEvent
chat: ChatEvent,
openPopup: OpenPopupEvent
@ -46,7 +45,8 @@ export type IframeEventMap = {
getDataLayer: undefined
loadSound: LoadSoundEvent
playSound: PlaySoundEvent
stopSound: null,
stopSound: null
setTiles: SetTilesEvent
getState: undefined,
registerMenuCommand: MenuItemRegisterEvent
}

View File

@ -5,6 +5,8 @@ import * as tg from "generic-type-guard";
export const isOpenCoWebsite =
new tg.IsInterface().withProperties({
url: tg.isString,
allowApi: tg.isBoolean,
allowPolicy: tg.isString,
}).get();
/**

View File

@ -0,0 +1,15 @@
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.isNumber, tg.isString),
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>;

View File

@ -18,7 +18,6 @@ import {
TypedMessageEvent
} from "./Events/IframeEvent";
import type {UserInputChatEvent} from "./Events/UserInputChatEvent";
//import { isLoadPageEvent } from './Events/LoadPageEvent';
import {isPlaySoundEvent, PlaySoundEvent} from "./Events/PlaySoundEvent";
import {isStopSoundEvent, StopSoundEvent} from "./Events/StopSoundEvent";
import {isLoadSoundEvent, LoadSoundEvent} from "./Events/LoadSoundEvent";
@ -30,6 +29,7 @@ import type {GameStateEvent} from "./Events/GameStateEvent";
import type {HasPlayerMovedEvent} from "./Events/HasPlayerMovedEvent";
import {isLoadPageEvent} from "./Events/LoadPageEvent";
import {handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent} from "./Events/ui/MenuItemRegisterEvent";
import {SetTilesEvent, isSetTilesEvent} from "./Events/SetTilesEvent";
/**
* Listens to messages from iframes and turn those messages into easy to use observables.
@ -103,6 +103,9 @@ class IframeListener {
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
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 iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
private readonly scripts = new Map<string, HTMLIFrameElement>();
@ -123,11 +126,20 @@ class IframeListener {
}
}
const payload = message.data;
if (foundSrc === 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;
}
const payload = message.data;
foundSrc = this.getBaseUrl(foundSrc, message.source);
if (isIframeEventWrapper(payload)) {
if (payload.type === 'showLayer' && isLayerEvent(payload.data)) {
this._showLayerStream.next(payload.data);
@ -161,7 +173,7 @@ class IframeListener {
this._loadSoundStream.next(payload.data);
}
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
scriptUtils.openCoWebsite(payload.data.url, foundSrc);
scriptUtils.openCoWebsite(payload.data.url, foundSrc, payload.data.allowApi, payload.data.allowPolicy);
}
else if (payload.type === 'closeCoWebSite') {
@ -190,6 +202,8 @@ class IframeListener {
this._unregisterMenuCommandStream.next(data);
})
handleMenuItemRegistrationEvent(payload.data)
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
this._setTilesStream.next(payload.data);
}
}
}, false);
@ -274,6 +288,15 @@ class IframeListener {
}
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 {
return 'script' + btoa(scriptUrl);
}

View File

@ -11,8 +11,8 @@ class ScriptUtils {
}
public openCoWebsite(url: string, base: string) {
coWebsiteManager.loadCoWebsite(url, base);
public openCoWebsite(url: string, base: string, api: boolean, policy: string) {
coWebsiteManager.loadCoWebsite(url, base, api, policy);
}
public closeCoWebSite(){

View File

@ -1,30 +1,30 @@
import type { ChatEvent } from '../Events/ChatEvent'
import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent'
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'
import type { ChatEvent } from "../Events/ChatEvent";
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks";
import { Subject } from "rxjs";
const chatStream = new Subject<string>();
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
callbacks = [apiCallback({
export class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
callbacks = [
apiCallback({
callback: (event: UserInputChatEvent) => {
chatStream.next(event.message);
},
type: "userInputChat",
typeChecker: isUserInputChatEvent
})]
typeChecker: isUserInputChatEvent,
}),
];
sendChatMessage(message: string, author: string) {
sendToWorkadventure({
type: 'chat',
type: "chat",
data: {
'message': message,
'author': author
}
})
message: message,
author: author,
},
});
}
/**
@ -35,4 +35,4 @@ class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatC
}
}
export default new WorkadventureChatCommands()
export default new WorkadventureChatCommands();

View File

@ -1,16 +1,15 @@
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
callbacks = []
export class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
callbacks = [];
disablePlayerControls(): void {
sendToWorkadventure({ 'type': 'disablePlayerControls', data: null });
sendToWorkadventure({ type: "disablePlayerControls", data: null });
}
restorePlayerControls(): void {
sendToWorkadventure({ 'type': 'restorePlayerControls', data: null });
sendToWorkadventure({ type: "restorePlayerControls", data: null });
}
}
export default new WorkadventureControlsCommands();

View File

@ -1,57 +1,56 @@
import type { GoToPageEvent } from '../Events/GoToPageEvent';
import type { OpenTabEvent } from '../Events/OpenTabEvent';
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
import type { GoToPageEvent } from "../Events/GoToPageEvent";
import type { OpenTabEvent } from "../Events/OpenTabEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent";
import type { LoadPageEvent } from "../Events/LoadPageEvent";
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
callbacks = []
export class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
callbacks = [];
openTab(url: string): void {
sendToWorkadventure({
"type": 'openTab',
"data": {
url
}
type: "openTab",
data: {
url,
},
});
}
goToPage(url: string): void {
sendToWorkadventure({
"type": 'goToPage',
"data": {
url
}
type: "goToPage",
data: {
url,
},
});
}
goToRoom(url: string): void {
sendToWorkadventure({
"type": 'loadPage',
"data": {
url
}
type: "loadPage",
data: {
url,
},
});
}
openCoWebSite(url: string): void {
openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
sendToWorkadventure({
"type": 'openCoWebSite',
"data": {
url
}
type: "openCoWebSite",
data: {
url,
allowApi,
allowPolicy,
},
});
}
closeCoWebSite(): void {
sendToWorkadventure({
"type": 'closeCoWebSite',
data: null
type: "closeCoWebSite",
data: null,
});
}
}
export default new WorkadventureNavigationCommands();

View File

@ -6,23 +6,23 @@ import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent";
const moveStream = new Subject<HasPlayerMovedEvent>();
class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
callbacks = [
apiCallback({
type: 'hasPlayerMoved',
type: "hasPlayerMoved",
typeChecker: isHasPlayerMovedEvent,
callback: (payloadData) => {
moveStream.next(payloadData);
}
},
}),
]
];
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
moveStream.subscribe(callback);
sendToWorkadventure({
type: 'onPlayerMove',
data: null
})
type: "onPlayerMove",
data: null,
});
}
}

View File

@ -1,87 +1,92 @@
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, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks";
import type {LayerEvent} from "../Events/LayerEvent";
import type {SetPropertyEvent} from "../Events/setPropertyEvent";
import type {GameStateEvent} from "../Events/GameStateEvent";
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
import type { DataLayerEvent } from "../Events/DataLayerEvent";
import {isGameStateEvent} from "../Events/GameStateEvent";
import {isDataLayerEvent} from "../Events/DataLayerEvent";
import type { GameStateEvent } from "../Events/GameStateEvent";
const enterStreams: 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 stateResolvers = new Subject<GameStateEvent>();
let immutableData: GameStateEvent;
let immutableDataPromise: Promise<GameStateEvent> | undefined = undefined;
interface Room {
id: string,
mapUrl: string,
map: ITiledMap,
startLayer: string | null
id: string;
mapUrl: string;
map: ITiledMap;
startLayer: string | null;
}
interface User {
id: string | undefined,
nickName: string | null,
tags: string[]
id: string | undefined;
nickName: string | null;
tags: string[];
}
interface TileDescriptor {
x: number
y: number
tile: number | string
layer: string
}
function getGameState(): Promise<GameStateEvent> {
if (immutableData) {
return Promise.resolve(immutableData);
}
else {
return new Promise<GameStateEvent>((resolver, thrower) => {
if (immutableDataPromise === undefined) {
immutableDataPromise = new Promise<GameStateEvent>((resolver, thrower) => {
stateResolvers.subscribe(resolver);
sendToWorkadventure({ type: "getState", data: null });
})
});
}
return immutableDataPromise;
}
function getDataLayer(): Promise<DataLayerEvent> {
return new Promise<DataLayerEvent>((resolver, thrower) => {
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 = [
apiCallback({
callback: (payloadData: EnterLeaveEvent) => {
enterStreams.get(payloadData.name)?.next();
},
type: "enterEvent",
typeChecker: isEnterLeaveEvent
typeChecker: isEnterLeaveEvent,
}),
apiCallback({
type: "leaveEvent",
typeChecker: isEnterLeaveEvent,
callback: (payloadData) => {
leaveStreams.get(payloadData.name)?.next();
}
},
}),
apiCallback({
type: "gameState",
typeChecker: isGameStateEvent,
callback: (payloadData) => {
stateResolvers.next(payloadData);
}
},
}),
apiCallback({
type: "dataLayer",
typeChecker: isDataLayerEvent,
callback: (payloadData) => {
dataLayerResolver.next(payloadData);
}
},
}),
]
];
onEnterZone(name: string, callback: () => void): void {
let subject = enterStreams.get(name);
@ -90,7 +95,6 @@ class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomC
enterStreams.set(name, subject);
}
subject.subscribe(callback);
}
onLeaveZone(name: string, callback: () => void): void {
let subject = leaveStreams.get(name);
@ -101,35 +105,45 @@ class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomC
subject.subscribe(callback);
}
showLayer(layerName: string): void {
sendToWorkadventure({type: 'showLayer', data: {'name': layerName}});
sendToWorkadventure({ type: "showLayer", data: { name: layerName } });
}
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 {
sendToWorkadventure({
type: 'setProperty',
type: "setProperty",
data: {
'layerName': layerName,
'propertyName': propertyName,
'propertyValue': propertyValue,
}
})
layerName: layerName,
propertyName: propertyName,
propertyValue: propertyValue,
},
});
}
getCurrentRoom(): Promise<Room> {
return getGameState().then((gameState) => {
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> {
return getGameState().then((gameState) => {
return { id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags };
});
}
setTiles(tiles: TileDescriptor[]) {
sendToWorkadventure({
type: 'setTiles',
data: tiles
})
}
}
export default new WorkadventureRoomCommands();

View File

@ -1,17 +1,15 @@
import type { LoadSoundEvent } from '../Events/LoadSoundEvent';
import type { PlaySoundEvent } from '../Events/PlaySoundEvent';
import type { StopSoundEvent } from '../Events/StopSoundEvent';
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
import type { StopSoundEvent } from "../Events/StopSoundEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { Sound } from "./Sound/Sound";
class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
callbacks = []
export class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
callbacks = [];
loadSound(url: string): Sound {
return new Sound(url);
}
}
export default new WorkadventureSoundCommands();

View File

@ -1,29 +1,31 @@
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent';
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent';
import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent';
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
import { isButtonClickedEvent } from "../Events/ButtonClickedEvent";
import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent";
import type { MenuItemRegisterEvent } from "../Events/ui/MenuItemRegisterEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks";
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
import { Popup } from "./Ui/Popup";
let popupId = 0;
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 {
zone: string
objectLayerName?: string,
popupText: string,
delay?: number
popupOptions: Array<ButtonDescriptor>
zone: string;
objectLayerName?: string;
popupText: string;
delay?: number;
popupOptions: Array<ButtonDescriptor>;
}
class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
callbacks = [apiCallback({
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
callbacks = [
apiCallback({
type: "buttonClickedEvent",
typeChecker: isButtonClickedEvent,
callback: (payloadData) => {
@ -35,19 +37,19 @@ class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiComma
if (callback) {
callback(popup);
}
}
},
}),
apiCallback({
type: "menuItemClicked",
typeChecker: isMenuItemClickedEvent,
callback: event => {
callback: (event) => {
const callback = menuCallbacks.get(event.menuItem);
if (callback) {
callback(event.menuItem)
callback(event.menuItem);
}
}
})];
},
}),
];
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
popupId++;
@ -66,40 +68,40 @@ class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiComma
}
sendToWorkadventure({
'type': 'openPopup',
'data': {
type: "openPopup",
data: {
popupId,
targetObject,
message,
buttons: buttons.map((button) => {
return {
label: button.label,
className: button.className
className: button.className,
};
})
}
}),
},
});
popups.set(popupId, popup)
popups.set(popupId, popup);
return popup;
}
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
menuCallbacks.set(commandDescriptor, callback);
sendToWorkadventure({
'type': 'registerMenuCommand',
'data': {
menutItem: commandDescriptor
}
type: "registerMenuCommand",
data: {
menutItem: commandDescriptor,
},
});
}
displayBubble(): void {
sendToWorkadventure({ 'type': 'displayBubble', data: null });
sendToWorkadventure({ type: "displayBubble", data: null });
}
removeBubble(): void {
sendToWorkadventure({ 'type': 'removeBubble', data: null });
sendToWorkadventure({ type: "removeBubble", data: null });
}
}

View File

@ -1,11 +1,11 @@
<script lang="typescript">
import type { Game } from "../../Phaser/Game/Game";
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
import {activeRowStore} from "../../Stores/CustomCharacterStore";
export let game: Game;
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
let activeRow = customCharacterScene.activeRow;
function selectLeft() {
customCharacterScene.moveCursorHorizontally(-1);
@ -17,12 +17,10 @@
function selectUp() {
customCharacterScene.moveCursorVertically(-1);
activeRow = customCharacterScene.activeRow;
}
function selectDown() {
customCharacterScene.moveCursorVertically(1);
activeRow = customCharacterScene.activeRow;
}
function previousScene() {
@ -44,16 +42,16 @@
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> &gt; </button>
</section>
<section class="action">
{#if activeRow === 0}
{#if $activeRowStore === 0}
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
{/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>
{/if}
{#if activeRow === 5}
{#if $activeRowStore === 5}
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
{/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>
{/if}
</section>

View File

@ -3,50 +3,58 @@ import type {CharacterTexture} from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
export interface FrameConfig {
frameWidth: number,
frameHeight: number,
frameWidth: number;
frameHeight: number;
}
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
const returnArray: BodyResourceDescriptionInterface[][] = [];
LAYERS.forEach(layer => {
LAYERS.forEach((layer) => {
const layerArray: BodyResourceDescriptionInterface[] = [];
Object.values(layer).forEach((textureDescriptor) => {
layerArray.push(textureDescriptor);
load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
})
returnArray.push(layerArray)
});
returnArray.push(layerArray);
});
return returnArray;
}
};
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(PLAYER_RESOURCES);
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 });
});
return returnArray;
}
};
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
const name = 'customCharacterTexture'+texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
export const loadCustomTexture = (
loaderPlugin: LoaderPlugin,
texture: CharacterTexture
): Promise<BodyResourceDescriptionInterface> => {
const name = "customCharacterTexture" + texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
frameWidth: 32,
frameHeight: 32
frameHeight: 32,
});
}
};
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
export const lazyLoadPlayerCharacterTextures = (
loadPlugin: LoaderPlugin,
texturekeys: Array<string | BodyResourceDescriptionInterface>
): Promise<string[]> => {
const promisesList: Promise<unknown>[] = [];
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
try {
//TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, {
promisesList.push(
createLoadingPromise(loadPlugin, playerResourceDescriptor, {
frameWidth: 32,
frameHeight: 32
}));
frameHeight: 32,
})
);
}
} catch (err) {
console.error(err);
@ -59,16 +67,24 @@ export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, textur
} else {
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 (typeof textureKey !== 'string' && textureKey.img) {
//If the loading fail, we render the default model instead.
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;
}
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name;
const playerResource = PLAYER_RESOURCES[textureName];
if (playerResource !== undefined) return playerResource;
@ -76,15 +92,33 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
const playerResource = LAYERS[i][textureName];
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) => {
return new Promise<BodyResourceDescriptionInterface>((res) => {
export const createLoadingPromise = (
loadPlugin: LoaderPlugin,
playerResourceDescriptor: BodyResourceDescriptionInterface,
frameConfig: FrameConfig
) => {
return new Promise<BodyResourceDescriptionInterface>((res, rej) => {
console.log("count", loadPlugin.listenerCount("loaderror"));
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor);
}
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);
});
}
};

View File

@ -13,8 +13,9 @@ export class GameMap {
private key: number | undefined;
private lastProperties = new Map<string, string | boolean | number>();
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 phaserLayers: TilemapLayer[] = [];
@ -36,6 +37,9 @@ export class GameMap {
if (tile.properties) {
this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties
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") {
this.exitUrls.push(prop.value);
}
@ -130,6 +134,10 @@ export class GameMap {
return this.map;
}
private getTileProperty(index: number): Array<ITiledMapLayerProperty> {
return this.tileSetPropertyMap[index];
}
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);
if (callbacksArray !== undefined) {
@ -165,4 +173,50 @@ export class GameMap {
}
}
private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void {
const fLayer = this.findLayer(layer);
if ( fLayer == undefined ) {
console.error("The layer that you want to change doesn't exist.");
return;
}
if (fLayer.type !== 'tilelayer') {
console.error("The 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 that you want to change is only readable.");
return;
}
fLayer.data[x+y*fLayer.height] = index;
}
public putTile(tile: string | number, x: number, y: number, layer: string): void {
const phaserLayer = this.findPhaserLayer(layer);
if ( phaserLayer ) {
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 === "true") {
phaserTile.setCollision(true);
}
}
}
else {
console.error("The tile that you want to place doesn't exist.");
}
}
else {
console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer.");
}
}
private getIndexForTileType(tile: string | number): number | undefined {
if (typeof tile == "number") {
return tile;
}
return this.tileNameMap.get(tile);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,11 @@ import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import { AbstractCharacterScene } from "./AbstractCharacterScene";
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
import { SelectCharacterSceneName } from "./SelectCharacterScene";
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
import { waScaleManager } from "../Services/WaScaleManager";
import { isMobile } from "../../Enum/EnvironmentVariable";
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
import { get } from "svelte/store";
export const CustomizeSceneName = "CustomizeScene";
@ -21,7 +22,6 @@ export class CustomizeScene extends AbstractCharacterScene {
private selectedLayers: number[] = [0];
private containersRow: CustomizedCharacter[][] = [];
public activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = [];
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
@ -31,16 +31,19 @@ export class CustomizeScene extends AbstractCharacterScene {
constructor() {
super({
key: CustomizeSceneName
key: CustomizeSceneName,
});
}
preload() {
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
throw 'Texture level is null';
if (
bodyResourceDescription.level == undefined ||
bodyResourceDescription.level < 0 ||
bodyResourceDescription.level > 5
) {
throw "Texture level is null";
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
@ -50,14 +53,13 @@ export class CustomizeScene extends AbstractCharacterScene {
this.layers = loadAllLayers(this.load);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
addLoader(this);
}
create() {
customCharacterSceneVisibleStore.set(true);
this.events.addListener('wake', () => {
this.events.addListener("wake", () => {
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
customCharacterSceneVisibleStore.set(true);
@ -66,8 +68,13 @@ export class CustomizeScene extends AbstractCharacterScene {
waScaleManager.saveZoom();
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.setStrokeStyle(2, 0xFFFFFF);
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.setStrokeStyle(2, 0xffffff);
this.add.existing(this.Rectangle);
this.createCustomizeLayer(0, 0, 0);
@ -78,24 +85,24 @@ export class CustomizeScene extends AbstractCharacterScene {
this.createCustomizeLayer(0, 0, 5);
this.moveLayers();
this.input.keyboard.on('keyup-ENTER', () => {
this.input.keyboard.on("keyup-ENTER", () => {
this.nextSceneToCamera();
});
this.input.keyboard.on('keyup-BACKSPACE', () => {
this.input.keyboard.on("keyup-BACKSPACE", () => {
this.backToPreviousScene();
});
// 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
// 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-LEFT', () => this.moveHorizontally = -1);
this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1);
this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1);
this.input.keyboard.on("keyup-RIGHT", () => (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-UP", () => (this.moveVertically = -1));
const customCursorPosition = localUserStore.getCustomCursorPosition();
if (customCursorPosition) {
this.activeRow = customCursorPosition.activeRow;
activeRowStore.set(customCursorPosition.activeRow);
this.selectedLayers = customCursorPosition.selectedLayers;
this.moveLayers();
this.updateSelectedLayer();
@ -113,11 +120,11 @@ export class CustomizeScene extends AbstractCharacterScene {
}
private doMoveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index;
if (this.selectedLayers[this.activeRow] < 0) {
this.selectedLayers[this.activeRow] = 0
} else if(this.selectedLayers[this.activeRow] > this.layers[this.activeRow].length - 1) {
this.selectedLayers[this.activeRow] = this.layers[this.activeRow].length - 1
this.selectedLayers[get(activeRowStore)] += index;
if (this.selectedLayers[get(activeRowStore)] < 0) {
this.selectedLayers[get(activeRowStore)] = 0;
} else if (this.selectedLayers[get(activeRowStore)] > this.layers[get(activeRowStore)].length - 1) {
this.selectedLayers[get(activeRowStore)] = this.layers[get(activeRowStore)].length - 1;
}
this.moveLayers();
this.updateSelectedLayer();
@ -125,19 +132,18 @@ export class CustomizeScene extends AbstractCharacterScene {
}
private doMoveCursorVertically(index: number): void {
this.activeRow += index;
if (this.activeRow < 0) {
this.activeRow = 0
} else if (this.activeRow > this.layers.length - 1) {
this.activeRow = this.layers.length - 1
activeRowStore.set(get(activeRowStore) + index);
if (get(activeRowStore) < 0) {
activeRowStore.set(0);
} else if (get(activeRowStore) > this.layers.length - 1) {
activeRowStore.set(this.layers.length - 1);
}
this.moveLayers();
this.saveInLocalStorage();
}
private saveInLocalStorage() {
localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers);
localUserStore.setCustomCursorPosition(get(activeRowStore), this.selectedLayers);
}
/**
@ -207,12 +213,11 @@ export class CustomizeScene extends AbstractCharacterScene {
selectedX = 0;
}
this.containersRow[i][j].x = screenCenterX + (j - selectedX) * 40;
this.containersRow[i][j].y = screenCenterY + (i - this.activeRow) * 40;
const alpha1 = Math.abs(selectedX - j)*47*2/screenWidth;
const alpha2 = Math.abs(this.activeRow - i)*49*2/screenHeight;
this.containersRow[i][j].y = screenCenterY + (i - get(activeRowStore)) * 40;
const alpha1 = (Math.abs(selectedX - j) * 47 * 2) / screenWidth;
const alpha2 = (Math.abs(get(activeRowStore) - i) * 49 * 2) / screenHeight;
this.containersRow[i][j].setAlpha((1 - alpha1) * (1 - alpha2));
}
}
}
@ -237,7 +242,6 @@ export class CustomizeScene extends AbstractCharacterScene {
}
update(time: number, delta: number): void {
if (this.lazyloadingAttempt) {
this.moveLayers();
this.lazyloadingAttempt = false;
@ -253,9 +257,6 @@ export class CustomizeScene extends AbstractCharacterScene {
}
}
public onResize(): void {
this.moveLayers();
@ -279,7 +280,7 @@ export class CustomizeScene extends AbstractCharacterScene {
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.events.removeListener('wake');
this.events.removeListener("wake");
gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
}

View File

@ -1,3 +1,5 @@
import { derived, writable, Writable } from "svelte/store";
export const customCharacterSceneVisibleStore = writable(false);
export const activeRowStore = writable(0);

View File

@ -16,6 +16,7 @@ import player from "./Api/iframe/player";
import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor";
import type { Popup } from "./Api/iframe/Ui/Popup";
import type { Sound } from "./Api/iframe/Sound/Sound";
import {sendToWorkadventure} from "./Api/iframe/IframeApiContribution";
const wa = {
ui,
@ -36,6 +37,7 @@ const wa = {
console.warn('Method WA.sendChatMessage is deprecated. Please use WA.chat.sendChatMessage instead');
chat.sendChatMessage(message, author);
},
/**
* @deprecated Use WA.chat.disablePlayerControls instead
*/
@ -103,9 +105,9 @@ const wa = {
/**
* @deprecated Use WA.nav.openCoWebSite instead
*/
openCoWebSite(url: string): void {
openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
console.warn('Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead');
nav.openCoWebSite(url);
nav.openCoWebSite(url, allowApi, allowPolicy);
},
/**

View File

@ -1,35 +1,37 @@
import type { Configuration } from "webpack";
import type WebpackDevServer from "webpack-dev-server";
import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import sveltePreprocess from 'svelte-preprocess';
import path from "path";
import webpack from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import sveltePreprocess from "svelte-preprocess";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin';
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
import { DISPLAY_TERMS_OF_USE } from "./src/Enum/EnvironmentVariable";
const mode = process.env.NODE_ENV ?? 'development';
const isProduction = mode === 'production';
const mode = process.env.NODE_ENV ?? "development";
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
const isProduction = mode === "production";
const isDevelopment = !isProduction;
const entries: { [key: string]: string } = {};
if (!buildNpmTypingsForApi) {
entries.main = "./src/index.ts";
}
entries.iframe_api = "./src/iframe_api.ts";
module.exports = {
entry: {
'main': './src/index.ts',
'iframe_api': './src/iframe_api.ts'
},
entry: entries,
mode: mode,
devtool: isDevelopment ? 'inline-source-map' : 'source-map',
devtool: isDevelopment ? "inline-source-map" : "source-map",
devServer: {
contentBase: './dist',
host: '0.0.0.0',
sockPort: 443,
disableHostCheck: true,
historyApiFallback: {
rewrites: [
{ from: /^_\/.*$/, to: '/index.html' }
],
disableDotRule: true
rewrites: [{ from: /^_\/.*$/, to: "/index.html" }],
disableDotRule: true,
},
},
module: {
@ -38,22 +40,28 @@ module.exports = {
test: /\.tsx?$/,
//use: 'ts-loader',
exclude: /node_modules/,
loader: 'ts-loader',
loader: "ts-loader",
options: {
transpileOnly: true,
transpileOnly: !buildNpmTypingsForApi,
compilerOptions: {
declaration: buildNpmTypingsForApi,
},
},
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader, {
loader: 'css-loader',
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
//url: false,
sourceMap: true
}
}, 'sass-loader'],
sourceMap: true,
},
},
"sass-loader",
],
},
{
test: /\.css$/,
@ -61,23 +69,23 @@ module.exports = {
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
loader: "css-loader",
options: {
//url: false,
sourceMap: true
}
}
]
sourceMap: true,
},
},
],
},
{
test: /\.(html|svelte)$/,
exclude: /node_modules/,
use: {
loader: 'svelte-loader',
loader: "svelte-loader",
options: {
compilerOptions: {
// Dev mode must be enabled for HMR to work!
dev: isDevelopment
dev: isDevelopment,
},
emitCss: isProduction,
hotReload: isDevelopment,
@ -90,18 +98,27 @@ module.exports = {
scss: true,
sass: true,
}),
onwarn: function (warning: { code: string }, handleWarning: (warning: { code: string }) => void) {
onwarn: function (
warning: { code: string },
handleWarning: (warning: { code: string }) => void
) {
// See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782
if (warning.code === 'a11y-no-onchange') { return }
if (warning.code === 'a11y-autofocus') { return }
if (warning.code === 'a11y-media-has-caption') { return }
if (warning.code === "a11y-no-onchange") {
return;
}
if (warning.code === "a11y-autofocus") {
return;
}
if (warning.code === "a11y-media-has-caption") {
return;
}
// process as usual
handleWarning(warning);
}
}
}
},
},
},
},
// Required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4
@ -109,51 +126,49 @@ module.exports = {
{
test: /node_modules\/svelte\/.*\.mjs$/,
resolve: {
fullySpecified: false
}
fullySpecified: false,
},
},
{
test: /\.(eot|svg|png|gif|jpg)$/,
exclude: /node_modules/,
type: 'asset'
type: "asset",
},
{
test: /\.(woff(2)?|ttf)$/,
type: 'asset',
type: "asset",
generator: {
filename: 'fonts/[name][ext]'
}
}
filename: "fonts/[name][ext]",
},
},
],
},
resolve: {
alias: {
svelte: path.resolve('node_modules', 'svelte')
svelte: path.resolve("node_modules", "svelte"),
},
extensions: [ '.tsx', '.ts', '.js', '.svelte' ],
mainFields: ['svelte', 'browser', 'module', 'main']
extensions: [".tsx", ".ts", ".js", ".svelte"],
mainFields: ["svelte", "browser", "module", "main"],
},
output: {
filename: (pathData) => {
// Add a content hash only for the main bundle.
// We want the iframe_api.js file to keep its name as it will be referenced from outside iframes.
return pathData.chunk?.name === 'main' ? 'js/[name].[contenthash].js': '[name].js';
return pathData.chunk?.name === "main" ? "js/[name].[contenthash].js" : "[name].js";
},
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
path: path.resolve(__dirname, "dist"),
publicPath: "/",
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ForkTsCheckerWebpackPlugin({
eslint: {
files: './src/**/*.ts'
}
files: "./src/**/*.ts",
},
}),
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.tmpl.html.tmp',
new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }),
new HtmlWebpackPlugin({
template: "./dist/index.tmpl.html.tmp",
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
@ -161,34 +176,32 @@ module.exports = {
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
useShortDoctype: true,
},
chunks: ['main']
}
),
chunks: ["main"],
}),
new webpack.ProvidePlugin({
Phaser: 'phaser'
Phaser: "phaser",
}),
new NodePolyfillPlugin(),
new webpack.EnvironmentPlugin({
'API_URL': null,
'SKIP_RENDER_OPTIMIZATIONS': false,
'DISABLE_NOTIFICATIONS': false,
'PUSHER_URL': undefined,
'UPLOADER_URL': null,
'ADMIN_URL': null,
'DEBUG_MODE': null,
'STUN_SERVER': null,
'TURN_SERVER': null,
'TURN_USER': null,
'TURN_PASSWORD': null,
'JITSI_URL': null,
'JITSI_PRIVATE_MODE': null,
'START_ROOM_URL': null,
'MAX_USERNAME_LENGTH': 8,
'MAX_PER_GROUP': 4,
'DISPLAY_TERMS_OF_USE': false,
})
API_URL: null,
SKIP_RENDER_OPTIMIZATIONS: false,
DISABLE_NOTIFICATIONS: false,
PUSHER_URL: undefined,
UPLOADER_URL: null,
ADMIN_URL: null,
DEBUG_MODE: null,
STUN_SERVER: null,
TURN_SERVER: null,
TURN_USER: null,
TURN_PASSWORD: null,
JITSI_URL: null,
JITSI_PRIVATE_MODE: null,
START_ROOM_URL: null,
MAX_USERNAME_LENGTH: 8,
MAX_PER_GROUP: 4,
DISPLAY_TERMS_OF_USE: false,
}),
],
} as Configuration & WebpackDevServer.Configuration;

View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<script>
var script = document.createElement('script');
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
// We are OK in this precise case because the HTML page is hosted on the "maps" domain that contains only static files.
script.setAttribute('src', document.referrer + 'iframe_api.js');
document.head.appendChild(script);
window.addEventListener('load', () => {
WA.chat.sendChatMessage('The iframe opened by a script works !', 'Mr Robot');
})
</script>
</head>
<body>
<p>Website opened by script.</p>
</body>
</html>

View File

@ -0,0 +1 @@
WA.nav.openCoWebSite("cowebsiteAllowApi.html", true, "");

View File

@ -0,0 +1,98 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[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, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":2,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51],
"height":10,
"id":1,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":116.5,
"id":1,
"name":"",
"rotation":0,
"text":
{
"text":"Test : \nThe iframe is opened by script.\n\nResult : \nA message is send to the chat.",
"wrap":true
},
"type":"",
"visible":true,
"width":295.875,
"x":11.8125,
"y":188.5
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":4,
"name":"mushroom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
}],
"nextlayerid":5,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"script",
"type":"string",
"value":"cowebsiteAllowApi.js"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":32,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"tileset_dungeon.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"tileset_dungeon",
"spacing":0,
"tilecount":64,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.4,
"width":10
}

View File

@ -0,0 +1,230 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":[46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 33, 34, 34, 34, 34, 34, 34, 35, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 49, 50, 50, 50, 50, 50, 50, 51, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46],
"height":10,
"id":2,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 52, 52, 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],
"height":10,
"id":4,
"name":"metadata",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":5,
"name":"floorLayer",
"objects":[],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"data":[1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19],
"height":10,
"id":3,
"name":"wall",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
}],
"nextlayerid":6,
"nextobjectid":1,
"orientation":"orthogonal",
"properties":[
{
"name":"script",
"type":"string",
"value":"script.js"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":32,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"tileset_dungeon.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"TDungeon",
"spacing":0,
"tilecount":64,
"tileheight":32,
"tiles":[
{
"id":0,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":1,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":2,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":3,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":4,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":8,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":9,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":10,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":11,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":12,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":16,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":17,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":18,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":19,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":20,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.4,
"width":10
}

View File

@ -0,0 +1,9 @@
/*WA.getMapUrl().then((map) => {console.log('mapUrl : ', map)});
WA.getUuid().then((uuid) => {console.log('Uuid : ',uuid)});
WA.getRoomId().then((roomId) => console.log('roomID : ',roomId));*/
//WA.onPlayerMove(console.log);
WA.setProperty('metadata', 'openWebsite', 'https://fr.wikipedia.org/');
WA.getDataLayer().then((data) => {console.log('data 1 : ', data)});

View File

@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<script>
var script = document.createElement('script');
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
// We are OK in this precise case because the HTML page is hosted on the "maps" domain that contains only static files.
script.setAttribute('src', document.referrer + 'iframe_api.js');
document.head.appendChild(script);
window.addEventListener('load', () => {
WA.room.setTiles([
{x: 0, y: 0, tile: 92, layer: 'setTiles'},
{x: 0, y: 2, tile: 'Red', layer: 'setTiles'},
{x: 0, y: 3, tile: 99, layer: 'setTiles'},
{x: 0, y: 5, tile: 117, layer: 'setTiles'},
{x: 0, y: 6, tile: 117, layer: 'setTiles'},
{x: 0, y: 9, tile: 74, layer: 'setTiles'}
]);
WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
{x: 7, y: 4, tile: 109, layer: 'setTiles'},
{x: 8, y: 4, tile: 109, layer: 'setTiles'},
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'}
]);
});
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,348 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":10,
"infinite":false,
"layers":[
{
"data":[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, 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, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51],
"height":10,
"id":2,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":4,
"name":"metadata",
"opacity":1,
"properties":[
{
"name":"openWebsite",
"type":"string",
"value":"setTiles.html"
},
{
"name":"openWebsiteAllowApi",
"type":"bool",
"value":true
}],
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":8,
"name":"setTiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":5,
"name":"floorLayer",
"objects":[
{
"height":191.866671635267,
"id":1,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":9,
"text":"Test : \nWalk on the grass\n\nResult : \nThe Yellow Tile opens a Jitsi with Trigger.\n\nThe Red Tile opens cowebsite Wikipedia. The highest Red Tile is found by 'name' property index, the lowest by 'number' index.\n\nThe White Tiles are silent tiles. You cannot open a bubble in it. (Even if the other player didn't activate the script.)\n\nThe Pale Tile (Lowest) is an exitUrl tile to customMenu.json.\n\nThe Blue Tile are 'collides' tile. The two tiles in the center are 'number' index. The others are 'name' property index.\n",
"wrap":true
},
"type":"",
"visible":true,
"width":274.674838251912,
"x":32.5473600365393,
"y":128.305680721763
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":9,
"nextobjectid":2,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":32,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"tileset_dungeon.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"TDungeon",
"spacing":0,
"tilecount":64,
"tileheight":32,
"tiles":[
{
"id":0,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":1,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":2,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":3,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":4,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":8,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":9,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":10,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":11,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":12,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":16,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":17,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":18,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":19,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":20,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
},
{
"columns":8,
"firstgid":65,
"image":"floortileset.png",
"imageheight":288,
"imagewidth":256,
"margin":0,
"name":"Floor",
"spacing":0,
"tilecount":72,
"tileheight":32,
"tiles":[
{
"id":9,
"properties":[
{
"name":"exitUrl",
"type":"string",
"value":"customMenu.json"
}]
},
{
"id":27,
"properties":[
{
"name":"jitsiRoom",
"type":"string",
"value":"TEST"
},
{
"name":"jitsiTrigger",
"type":"string",
"value":"onaction"
},
{
"name":"jitsiUrl",
"type":"string",
"value":"meet.jit.si"
}]
},
{
"id":34,
"properties":[
{
"name":"name",
"type":"string",
"value":"Red"
},
{
"name":"openWebsite",
"type":"string",
"value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal"
}]
},
{
"id":40,
"properties":[
{
"name":"name",
"type":"string",
"value":""
}]
},
{
"id":44,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
},
{
"name":"name",
"type":"string",
"value":"blue"
}]
},
{
"id":52,
"properties":[
{
"name":"silent",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.4,
"width":10
}

View File

@ -1,22 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<script src="http://play.workadventure.localhost/iframe_api.js"></script>
<script>
var script = document.createElement('script');
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
// We are OK in this precise case because the HTML page is hosted on the "maps" domain that contains only static files.
script.setAttribute('src', document.referrer + 'iframe_api.js');
document.head.appendChild(script);
</script>
</head>
<body>
<button id="sendchat">Send chat message</button>
<script>
document.getElementById('sendchat').onclick = () => {
WA.sendChatMessage('Hello world!', 'Mr ROBOT');
WA.chat.sendChatMessage('Hello world!', 'Mr ROBOT');
}
</script>
<div id="chatSent"></div>
<script>
WA.onChatMessage((message => {
window.addEventListener('load', () => {
WA.chat.onChatMessage((message => {
const chatDiv = document.createElement('p');
chatDiv.innerText = message;
document.getElementById('chatSent').append(chatDiv);
}));
})
</script>
</body>
</html>

View File

@ -4,7 +4,7 @@
</head>
<body>
<label>Base test URL:</label>
<input id="baseurl" type="text" value="http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/" />
<input id="baseurl" type="text" value="" />
<table>
<tr>
<th>Result</th>
@ -106,6 +106,14 @@
<a href="#" class="testLink" data-testmap="help_camera_setting.json" target="_blank">Test the HelpCameraSettingScene</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-cowebsite-allowAPI"> Success <input type="radio" name="test-cowebsite-allowAPI"> Failure <input type="radio" name="test-cowebsite-allowAPI" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="Metadata/cowebsiteAllowApi.json" target="_blank">Test a iframe opened by a script can use Iframe API</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-custom-menu"> Success <input type="radio" name="test-custom-menu"> Failure <input type="radio" name="test-custom-menu" checked> Pending
@ -162,10 +170,41 @@
<a href="#" class="testLink" data-testmap="animated_tiles.json" target="_blank">Test animated tiles</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-cowebsite-allowAPI"> Success <input type="radio" name="test-cowebsite-allowAPI"> Failure <input type="radio" name="test-cowebsite-allowAPI" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="Metadata/cowebsiteAllowApi.json" target="_blank">Test cowebsite opened by script is allowed to use IFrame API</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-set-tiles"> Success <input type="radio" name="test-set-tiles"> Failure <input type="radio" name="test-set-tiles" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="Metadata/setTiles.json" target="_blank">Test set tiles</a>
</td>
</tr>
</table>
<script>
const baseInput = document.getElementById('baseurl');
let host = window.location.host;
let playHost;
if (host.startsWith('maps-')) {
playHost = 'play-'+host.substr(5);
} else if (host.startsWith('maps.')) {
playHost = 'play.'+host.substr(5);
} else {
playHost = 'localhost';
}
let completeUrl = window.location.protocol + '//' + playHost + '/_/global/' + host + window.location.pathname;
baseInput.value = completeUrl;
baseInput.addEventListener('change', init);
function init() {

View File

@ -2,6 +2,6 @@
# yarn lockfile v1
husky@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e"
"husky@^6.0.0":
"resolved" "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz"
"version" "6.0.0"