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

This commit is contained in:
_Bastler
2021-10-29 19:57:51 +02:00
19 changed files with 1059 additions and 77 deletions
+74 -7
View File
@@ -2,6 +2,7 @@ import type { ITiledMap, ITiledMapLayer, ITiledMapProperty } from "../Map/ITiled
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
import { iframeListener } from "../../Api/IframeListener";
export type PropertyChangeCallback = (
newValue: string | number | boolean | undefined,
@@ -9,14 +10,25 @@ export type PropertyChangeCallback = (
allProps: Map<string, string | boolean | number>
) => void;
export type layerChangeCallback = (
layersChangedByAction: Array<ITiledMapLayer>,
allLayersOnNewPosition: Array<ITiledMapLayer>,
) => void;
/**
* A wrapper around a ITiledMap interface to provide additional capabilities.
* It is used to handle layer properties.
*/
export class GameMap {
// oldKey is the index of the previous tile.
private oldKey: number | undefined;
// key is the index of the current tile.
private key: number | undefined;
private lastProperties = new Map<string, string | boolean | number>();
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
private enterLayerCallbacks = Array<layerChangeCallback>();
private leaveLayerCallbacks = Array<layerChangeCallback>();
private tileNameMap = new Map<string, number>();
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
@@ -68,22 +80,32 @@ export class GameMap {
return [];
}
private getLayersByKey(key: number): Array<ITiledMapLayer> {
return this.flatLayers.filter(flatLayer => flatLayer.type === 'tilelayer' && flatLayer.data[key] !== 0);
}
/**
* Sets the position of the current player (in pixels)
* This will trigger events if properties are changing.
*/
public setPosition(x: number, y: number) {
this.oldKey = this.key;
const xMap = Math.floor(x / this.map.tilewidth);
const yMap = Math.floor(y / this.map.tileheight);
const key = xMap + yMap * this.map.width;
if (key === this.key) {
return;
}
this.key = key;
this.triggerAll();
this.triggerAllProperties();
this.triggerLayersChange();
}
private triggerAll(): void {
private triggerAllProperties(): void {
const newProps = this.getProperties(this.key ?? 0);
const oldProps = this.lastProperties;
this.lastProperties = newProps;
@@ -105,6 +127,36 @@ export class GameMap {
}
}
private triggerLayersChange() {
const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : [];
const layersByNewKey = this.key ? this.getLayersByKey(this.key) : [];
const enterLayers = new Set(layersByNewKey);
const leaveLayers = new Set(layersByOldKey);
enterLayers.forEach(layer => {
if (leaveLayers.has(layer)) {
leaveLayers.delete(layer);
enterLayers.delete(layer);
}
});
if (enterLayers.size > 0) {
const layerArray = Array.from(enterLayers);
for (const callback of this.enterLayerCallbacks) {
callback(layerArray, layersByNewKey);
}
}
if (leaveLayers.size > 0) {
const layerArray = Array.from(leaveLayers);
for (const callback of this.leaveLayerCallbacks) {
callback(layerArray, layersByNewKey);
}
}
}
public getCurrentProperties(): Map<string, string | boolean | number> {
return this.lastProperties;
}
@@ -167,7 +219,7 @@ export class GameMap {
newValue: string | number | boolean | undefined,
allProps: Map<string, string | boolean | number>
) {
const callbacksArray = this.callbacks.get(propName);
const callbacksArray = this.propertiesChangeCallbacks.get(propName);
if (callbacksArray !== undefined) {
for (const callback of callbacksArray) {
callback(newValue, oldValue, allProps);
@@ -179,14 +231,28 @@ export class GameMap {
* Registers a callback called when the user moves to a tile where the property propName is different from the last tile the user was on.
*/
public onPropertyChange(propName: string, callback: PropertyChangeCallback) {
let callbacksArray = this.callbacks.get(propName);
let callbacksArray = this.propertiesChangeCallbacks.get(propName);
if (callbacksArray === undefined) {
callbacksArray = new Array<PropertyChangeCallback>();
this.callbacks.set(propName, callbacksArray);
this.propertiesChangeCallbacks.set(propName, callbacksArray);
}
callbacksArray.push(callback);
}
/**
* Registers a callback called when the user moves inside another layer.
*/
public onEnterLayer(callback: layerChangeCallback) {
this.enterLayerCallbacks.push(callback);
}
/**
* Registers a callback called when the user moves outside another layer.
*/
public onLeaveLayer(callback: layerChangeCallback) {
this.leaveLayerCallbacks.push(callback);
}
public findLayer(layerName: string): ITiledMapLayer | undefined {
return this.flatLayers.find((layer) => layer.name === layerName);
}
@@ -284,7 +350,8 @@ export class GameMap {
}
property.value = propertyValue;
this.triggerAll();
this.triggerAllProperties();
this.triggerLayersChange();
}
/**
@@ -1,15 +1,32 @@
import type { GameScene } from "./GameScene";
import type { GameMap } from "./GameMap";
import { scriptUtils } from "../../Api/ScriptUtils";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { get } from 'svelte/store';
import {
ON_ACTION_TRIGGER_BUTTON,
TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES,
} from "../../WebRtc/LayoutManager";
import type { ITiledMapLayer } from "../Map/ITiledMap";
enum OpenCoWebsiteState {
LOADING,
OPENED,
MUST_BE_CLOSE,
}
interface OpenCoWebsite {
coWebsite: CoWebsite | undefined,
state: OpenCoWebsiteState
}
export class GameMapPropertiesListener {
private coWebsitesOpenByLayer = new Map<ITiledMapLayer, OpenCoWebsite>();
private coWebsitesActionTriggerByLayer = new Map<ITiledMapLayer, string>();
constructor(private scene: GameScene, private gameMap: GameMap) {}
register() {
@@ -36,42 +53,178 @@ export class GameMapPropertiesListener {
}
}
});
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
const handler = async () => {
if (newValue === undefined || newValue !== oldValue) {
layoutManagerActionStore.removeAction("openWebsite");
await coWebsiteManager.closeCoWebsites();
}
if (newValue !== undefined) {
// Open a new co-website by the property.
this.gameMap.onEnterLayer((newLayers) => {
const handler = () => {
newLayers.forEach(layer => {
if (!layer.properties) {
return;
}
let openWebsiteProperty: string | undefined;
let allowApiProperty: boolean | undefined;
let websitePolicyProperty: string | undefined;
let websiteWidthProperty: number | undefined;
let websitePositionProperty: number | undefined;
let websiteTriggerProperty: string | undefined;
let websiteTriggerMessageProperty: string | undefined;
layer.properties.forEach(property => {
switch(property.name) {
case 'openWebsite':
openWebsiteProperty = property.value as string | undefined;
break;
case 'openWebsiteAllowApi':
allowApiProperty = property.value as boolean | undefined;
break;
case 'openWebsitePolicy':
websitePolicyProperty = property.value as string | undefined;
break;
case 'openWebsiteWidth':
websiteWidthProperty = property.value as number | undefined;
break;
case 'openWebsitePosition':
websitePositionProperty = property.value as number | undefined;
break;
case TRIGGER_WEBSITE_PROPERTIES:
websiteTriggerProperty = property.value as string | undefined;
break;
case WEBSITE_MESSAGE_PROPERTIES:
websiteTriggerMessageProperty = property.value as string | undefined;
break;
}
});
if (!openWebsiteProperty) {
return;
}
const actionUuid = "openWebsite-" + (Math.random() + 1).toString(36).substring(7);
if (this.coWebsitesOpenByLayer.has(layer)) {
return;
}
this.coWebsitesOpenByLayer.set(layer, {
coWebsite: undefined,
state: OpenCoWebsiteState.LOADING,
});
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(
newValue as string,
openWebsiteProperty as string,
this.scene.MapUrlFile,
allProps.get("openWebsiteAllowApi") as boolean | undefined,
allProps.get("openWebsitePolicy") as string | undefined,
allProps.get("openWebsiteWidth") as number | undefined
);
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
websitePositionProperty,
).then(coWebsite => {
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
coWebsiteManager.closeCoWebsite(coWebsite);
this.coWebsitesOpenByLayer.delete(layer);
this.coWebsitesActionTriggerByLayer.delete(layer);
} else {
this.coWebsitesOpenByLayer.set(layer, {
coWebsite,
state: OpenCoWebsiteState.OPENED
});
}
});
layoutManagerActionStore.removeAction("openWebsite");
layoutManagerActionStore.removeAction(actionUuid);
};
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if (message === undefined) {
message = "Press SPACE or touch here to open web site";
if (websiteTriggerProperty && websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON) {
if (!websiteTriggerMessageProperty) {
websiteTriggerMessageProperty = "Press SPACE or touch here to open web site";
}
this.coWebsitesActionTriggerByLayer.set(layer, actionUuid);
layoutManagerActionStore.addAction({
uuid: "openWebsite",
uuid: actionUuid,
type: "message",
message: message,
message: websiteTriggerMessageProperty,
callback: () => openWebsiteFunction(),
userInputManager: this.scene.userInputManager,
});
} else {
openWebsiteFunction();
}
}
});
};
handler();
});
// Close opened co-websites on leave the layer who contain the property.
this.gameMap.onLeaveLayer((oldLayers) => {
const handler = () => {
oldLayers.forEach(layer => {
if (!layer.properties) {
return;
}
let openWebsiteProperty: string | undefined;
let websiteTriggerProperty: string | undefined;
layer.properties.forEach(property => {
switch(property.name) {
case 'openWebsite':
openWebsiteProperty = property.value as string | undefined;
break;
case TRIGGER_WEBSITE_PROPERTIES:
websiteTriggerProperty = property.value as string | undefined;
break;
}
});
if (!openWebsiteProperty) {
return;
}
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
if (!coWebsiteOpen) {
return;
}
if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) {
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
return;
}
if (coWebsiteOpen.state !== OpenCoWebsiteState.OPENED) {
return;
}
if (coWebsiteOpen.coWebsite !== undefined) {
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite);
}
this.coWebsitesOpenByLayer.delete(layer);
if (!websiteTriggerProperty) {
return;
}
const actionStore = get(layoutManagerActionStore);
const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer);
if (!actionTriggerUuid) {
return;
}
const action = actionStore && actionStore.length > 0 ?
actionStore.find(action => action.uuid === actionTriggerUuid) : undefined;
if (action) {
layoutManagerActionStore.removeAction(actionTriggerUuid);
}
});
};
handler();
+21 -1
View File
@@ -188,6 +188,8 @@ export class GameScene extends DirtyScene {
moving: false,
x: -1000,
y: -1000,
oldX: -1000,
oldY: -1000,
};
private gameMap!: GameMap;
@@ -766,6 +768,19 @@ export class GameScene extends DirtyScene {
//init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
// Init layer change listener
this.gameMap.onEnterLayer(layers => {
layers.forEach(layer => {
iframeListener.sendEnterLayerEvent(layer.name);
});
});
this.gameMap.onLeaveLayer(layers => {
layers.forEach(layer => {
iframeListener.sendLeaveLayerEvent(layer.name);
});
});
});
}
@@ -897,6 +912,7 @@ export class GameScene extends DirtyScene {
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange("zone", (newValue, oldValue) => {
if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string);
@@ -1787,7 +1803,11 @@ export class GameScene extends DirtyScene {
const playerMovement = new PlayerMovement(
{ x: player.x, y: player.y },
this.currentTick,
message.position,
{
...message.position,
oldX: undefined,
oldY: undefined,
},
this.currentTick + POSITION_DELAY
);
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
+2
View File
@@ -38,6 +38,8 @@ export class PlayerMovement {
return {
x,
y,
oldX: this.startPosition.x,
oldY: this.startPosition.y,
direction: this.endPosition.direction,
moving: true,
};