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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 880 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 933 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 978 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 985 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 713 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 848 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

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";
import { Subject } from "rxjs";
const chatStream = new Subject<string>();
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
callbacks = [apiCallback({
callback: (event: UserInputChatEvent) => {
chatStream.next(event.message);
},
type: "userInputChat",
typeChecker: isUserInputChatEvent
})]
export class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
callbacks = [
apiCallback({
callback: (event: UserInputChatEvent) => {
chatStream.next(event.message);
},
type: "userInputChat",
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 {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent";
import type {LoadPageEvent} from "../Events/LoadPageEvent";
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
callbacks = []
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";
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

@ -1,29 +1,29 @@
import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution";
import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent";
import {Subject} from "rxjs";
import {apiCallback} from "./registeredCallbacks";
import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent";
import { Subject } from "rxjs";
import { apiCallback } from "./registeredCallbacks";
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,
});
}
}
export default new WorkadventurePlayerCommands();
export default new WorkadventurePlayerCommands();

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 { ITiledMap } from "../../Phaser/Map/ITiledMap";
import type { DataLayerEvent } 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});
})
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 getDataLayer().then((mapJson) => {
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};
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 {Sound} from "./Sound/Sound";
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,53 +1,55 @@
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({
type: "buttonClickedEvent",
typeChecker: isButtonClickedEvent,
callback: (payloadData) => {
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
const popup = popups.get(payloadData.popupId);
if (popup === undefined) {
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
}
if (callback) {
callback(popup);
}
}
}),
apiCallback({
type: "menuItemClicked",
typeChecker: isMenuItemClickedEvent,
callback: event => {
const callback = menuCallbacks.get(event.menuItem);
if (callback) {
callback(event.menuItem)
}
}
})];
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
callbacks = [
apiCallback({
type: "buttonClickedEvent",
typeChecker: isButtonClickedEvent,
callback: (payloadData) => {
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
const popup = popups.get(payloadData.popupId);
if (popup === undefined) {
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
}
if (callback) {
callback(popup);
}
},
}),
apiCallback({
type: "menuItemClicked",
typeChecker: isMenuItemClickedEvent,
callback: (event) => {
const callback = menuCallbacks.get(event.menuItem);
if (callback) {
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

@ -1,90 +1,124 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type {CharacterTexture} from "../../Connexion/LocalUser";
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
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 => {
const layerArray:BodyResourceDescriptionInterface[] = [];
const returnArray: BodyResourceDescriptionInterface[][] = [];
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)
load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
});
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});
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[]> => {
const promisesList:Promise<unknown>[] = [];
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
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, {
frameWidth: 32,
frameHeight: 32
}));
promisesList.push(
createLoadingPromise(loadPlugin, playerResourceDescriptor, {
frameWidth: 32,
frameHeight: 32,
})
);
}
}catch (err){
} catch (err) {
console.error(err);
}
});
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
returnPromise = Promise.all(promisesList).then(() => texturekeys);
} 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;
for (let i=0; i<LAYERS.length;i++) {
for (let i = 0; i < LAYERS.length; i++) {
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

@ -1,18 +1,19 @@
import {EnableCameraSceneName} from "./EnableCameraScene";
import { EnableCameraSceneName } from "./EnableCameraScene";
import Rectangle = Phaser.GameObjects.Rectangle;
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager";
import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager";
import Sprite = Phaser.GameObjects.Sprite;
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {addLoader} from "../Components/Loader";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import { gameManager } from "../Game/GameManager";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { addLoader } from "../Components/Loader";
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 {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
import {CustomizedCharacter} from "../Entity/CustomizedCharacter";
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,31 +120,30 @@ 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();
this.saveInLocalStorage();
}
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
private doMoveCursorVertically(index: number): void {
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);
}
/**
@ -173,7 +179,7 @@ export class CustomizeScene extends AbstractCharacterScene {
* @param selectedItem, The number of the item select (0 for black body...)
*/
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem));
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber, selectedItem));
}
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
@ -188,7 +194,7 @@ export class CustomizeScene extends AbstractCharacterScene {
}
children.push(this.layers[j][layer].name);
}
}
}
return children;
}
@ -202,17 +208,16 @@ export class CustomizeScene extends AbstractCharacterScene {
const screenHeight = this.game.renderer.height;
for (let i = 0; i < this.containersRow.length; i++) {
for (let j = 0; j < this.containersRow[i].length; j++) {
let selectedX = this.selectedLayers[i];
if (selectedX === undefined) {
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].setAlpha((1 -alpha1)*(1 - alpha2));
let selectedX = this.selectedLayers[i];
if (selectedX === undefined) {
selectedX = 0;
}
this.containersRow[i][j].x = screenCenterX + (j - selectedX) * 40;
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));
}
}
}
@ -228,8 +233,8 @@ export class CustomizeScene extends AbstractCharacterScene {
}
private updateSelectedLayer() {
for(let i = 0; i < this.containersRow.length; i++){
for(let j = 0; j < this.containersRow[i].length; j++){
for (let i = 0; i < this.containersRow.length; i++) {
for (let j = 0; j < this.containersRow[i].length; j++) {
const children = this.getContainerChildren(i, j);
this.containersRow[i][j].updateSprites(children);
}
@ -237,8 +242,7 @@ export class CustomizeScene extends AbstractCharacterScene {
}
update(time: number, delta: number): void {
if(this.lazyloadingAttempt){
if (this.lazyloadingAttempt) {
this.moveLayers();
this.lazyloadingAttempt = false;
}
@ -253,38 +257,35 @@ export class CustomizeScene extends AbstractCharacterScene {
}
}
public onResize(): void {
public onResize(): void {
this.moveLayers();
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
}
public nextSceneToCamera(){
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
}
i++;
}
if (!areCharacterLayersValid(layers)) {
return;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.events.removeListener('wake');
gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
}
public backToPreviousScene(){
public nextSceneToCamera() {
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
}
i++;
}
if (!areCharacterLayersValid(layers)) {
return;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.events.removeListener("wake");
gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
}
public backToPreviousScene() {
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.scene.run(SelectCharacterSceneName);

View file

@ -1,3 +1,5 @@
import { derived, writable, Writable } from "svelte/store";
export const customCharacterSceneVisibleStore = writable(false);
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 { 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 {DISPLAY_TERMS_OF_USE} from "./src/Enum/EnvironmentVariable";
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,86 +126,82 @@ 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",
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
},
chunks: ["main"],
}),
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.tmpl.html.tmp',
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
},
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;