diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md
index 286f2ac7..b1d244da 100644
--- a/docs/maps/api-ui.md
+++ b/docs/maps/api-ui.md
@@ -86,4 +86,24 @@ WA.ui.registerMenuCommand("test", () => {

-
\ No newline at end of file
+
+
+
+
+### Awaiting User Confirmation (with space bar)
+
+```typescript
+triggerMessage(message: string): TriggerMessage
+```
+
+Displays a message at the bottom of the screen (that will disappear when space bar is pressed).
+
+Example:
+
+```javascript
+const triggerMessage = WA.ui.triggerMessage("press 'space' to confirm");
+setTimeout(()=>{
+ // later
+ triggerMessage.remove();
+},1000)
+```
\ No newline at end of file
diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts
index 7325f811..d2df4ded 100644
--- a/front/src/Api/Events/IframeEvent.ts
+++ b/front/src/Api/Events/IframeEvent.ts
@@ -18,6 +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 { MessageReferenceEvent, TriggerMessageEvent } from '../iframe/TriggerMessageEvent';
export interface TypedMessageEvent extends MessageEvent {
@@ -49,6 +50,9 @@ export type IframeEventMap = {
stopSound: null,
getState: undefined,
registerMenuCommand: MenuItemRegisterEvent
+
+ triggerMessage: TriggerMessageEvent
+ removeTriggerMessage: MessageReferenceEvent
}
export interface IframeEvent {
type: T;
@@ -68,6 +72,7 @@ export interface IframeResponseEventMap {
hasPlayerMoved: HasPlayerMovedEvent
dataLayer: DataLayerEvent
menuItemClicked: MenuItemClickedEvent
+ messageTriggered: MessageReferenceEvent
}
export interface IframeResponseEvent {
type: T;
diff --git a/front/src/Api/Events/ui/TriggerMessageEvent.ts b/front/src/Api/Events/ui/TriggerMessageEvent.ts
new file mode 100644
index 00000000..5b42b02e
--- /dev/null
+++ b/front/src/Api/Events/ui/TriggerMessageEvent.ts
@@ -0,0 +1,21 @@
+import * as tg from "generic-type-guard";
+
+export const triggerMessage = "triggerMessage"
+export const removeTriggerMessage = "removeTriggerMessage"
+
+export const isTriggerMessageEvent = new tg.IsInterface().withProperties({
+ message: tg.isString,
+ uuid: tg.isString
+}).get()
+
+
+export type TriggerMessageEvent = tg.GuardedType;
+
+
+export const isMessageReferenceEvent =
+ new tg.IsInterface().withProperties({
+ uuid: tg.isString
+ }).get();
+
+
+export type MessageReferenceEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/ui/TriggerMessageEventHandler.ts b/front/src/Api/Events/ui/TriggerMessageEventHandler.ts
new file mode 100644
index 00000000..d690dbc0
--- /dev/null
+++ b/front/src/Api/Events/ui/TriggerMessageEventHandler.ts
@@ -0,0 +1,42 @@
+import { Subject } from 'rxjs';
+import { iframeListener } from '../../IframeListener';
+import { isMessageReferenceEvent, isTriggerMessageEvent, MessageReferenceEvent, removeTriggerMessage, triggerMessage, TriggerMessageEvent } from './TriggerMessageEvent';
+import * as tg from "generic-type-guard";
+export function sendMessageTriggeredEvent(uuid: string) {
+ iframeListener.postMessage({
+ 'type': 'messageTriggered',
+ 'data': {
+ uuid,
+ } as MessageReferenceEvent
+ });
+}
+
+const _triggerMessageEvent: Subject = new Subject();
+const _removeTriggerMessageEvent: Subject = new Subject();
+
+export const triggerMessageEvent = _triggerMessageEvent.asObservable();
+
+export const removeTriggerMessageEvent = _removeTriggerMessageEvent.asObservable();
+
+const isTriggerMessageEventObject = new tg.IsInterface().withProperties({
+ type: tg.isSingletonString(triggerMessage),
+ data: isTriggerMessageEvent
+}).get()
+const isTriggerMessageRemoveEventObject = new tg.IsInterface().withProperties({
+ type: tg.isSingletonString(removeTriggerMessage),
+ data: isMessageReferenceEvent
+}).get()
+
+
+export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject)
+
+
+
+
+export function triggerMessageEventHandler(event: tg.GuardedType) {
+ if (isTriggerMessageEventObject(event)) {
+ _triggerMessageEvent.next(event.data)
+ } else if (isTriggerMessageRemoveEventObject(event)) {
+ _removeTriggerMessageEvent.next(event.data)
+ }
+}
\ No newline at end of file
diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts
index 9311d7b6..3320519a 100644
--- a/front/src/Api/IframeListener.ts
+++ b/front/src/Api/IframeListener.ts
@@ -1,14 +1,14 @@
-import {Subject} from "rxjs";
-import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
-import {HtmlUtils} from "../WebRtc/HtmlUtils";
-import type {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
-import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
-import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
-import type {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
-import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
-import {scriptUtils} from "./ScriptUtils";
-import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
-import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
+import { Subject } from "rxjs";
+import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
+import { HtmlUtils } from "../WebRtc/HtmlUtils";
+import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
+import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
+import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
+import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
+import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
+import { scriptUtils } from "./ScriptUtils";
+import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
+import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
import {
IframeEvent,
IframeEventMap,
@@ -17,19 +17,20 @@ import {
isIframeEventWrapper,
TypedMessageEvent
} from "./Events/IframeEvent";
-import type {UserInputChatEvent} from "./Events/UserInputChatEvent";
+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";
-import {isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
-import {isLayerEvent, LayerEvent} from "./Events/LayerEvent";
-import {isMenuItemRegisterEvent,} from "./Events/ui/MenuItemRegisterEvent";
-import type {DataLayerEvent} from "./Events/DataLayerEvent";
-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 { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
+import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
+import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
+import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent";
+import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
+import { isMenuItemRegisterEvent, } from "./Events/ui/MenuItemRegisterEvent";
+import type { DataLayerEvent } from "./Events/DataLayerEvent";
+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 { isTriggerMessageHandlerEvent, triggerMessageEventHandler } from './Events/ui/TriggerMessageEventHandler';
/**
* Listens to messages from iframes and turn those messages into easy to use observables.
@@ -190,6 +191,8 @@ class IframeListener {
this._unregisterMenuCommandStream.next(data);
})
handleMenuItemRegistrationEvent(payload.data)
+ } else if (isTriggerMessageHandlerEvent(payload)) {
+ triggerMessageEventHandler(payload)
}
}
}, false);
@@ -198,8 +201,8 @@ class IframeListener {
sendDataLayerEvent(dataLayerEvent: DataLayerEvent) {
this.postMessage({
- 'type' : 'dataLayer',
- 'data' : dataLayerEvent
+ 'type': 'dataLayer',
+ 'data': dataLayerEvent
})
}
diff --git a/front/src/Api/iframe/Ui/TriggerMessage.ts b/front/src/Api/iframe/Ui/TriggerMessage.ts
new file mode 100644
index 00000000..af0e20ce
--- /dev/null
+++ b/front/src/Api/iframe/Ui/TriggerMessage.ts
@@ -0,0 +1,51 @@
+
+import { removeTriggerMessage, triggerMessage, TriggerMessageEvent } from '../../Events/ui/TriggerMessageEvent';
+import { sendToWorkadventure } from '../IframeApiContribution';
+function uuidv4() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
+ const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+}
+
+export let triggerMessageInstance: TriggerMessage | undefined = undefined
+
+
+
+export class TriggerMessage {
+ uuid: string
+
+ constructor(private message: string, private callback: () => void) {
+ this.uuid = uuidv4()
+ if (triggerMessageInstance) {
+ triggerMessageInstance.remove();
+ }
+ triggerMessageInstance = this;
+ this.create();
+ }
+
+ create(): this {
+ sendToWorkadventure({
+ type: triggerMessage,
+ data: {
+ message: this.message,
+ uuid: this.uuid
+ } as TriggerMessageEvent
+ })
+ return this
+ }
+
+ remove() {
+ sendToWorkadventure({
+ type: removeTriggerMessage,
+ data: {
+ uuid: this.uuid
+ } as TriggerMessageEvent
+ })
+ triggerMessageInstance = undefined
+ }
+
+ trigger() {
+ this.callback();
+ }
+}
\ No newline at end of file
diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts
index c7655b84..834cc347 100644
--- a/front/src/Api/iframe/ui.ts
+++ b/front/src/Api/iframe/ui.ts
@@ -1,10 +1,11 @@
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent';
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent';
-import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent';
+import { isMessageReferenceEvent } from '../Events/ui/TriggerMessageEvent';
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
import { apiCallback } from "./registeredCallbacks";
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
import { Popup } from "./Ui/Popup";
+import { TriggerMessage, triggerMessageInstance } from './Ui/TriggerMessage';
let popupId = 0;
const popups: Map = new Map();
@@ -12,41 +13,41 @@ const popupCallbacks: Map> = new Map<
const menuCallbacks: Map void> = new Map()
-interface ZonedPopupOptions {
- zone: string
- objectLayerName?: string,
- popupText: string,
- delay?: number
- popupOptions: Array
-}
-
-
class WorkAdventureUiCommands extends IframeApiContribution {
- 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 + '"');
+ 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);
+ }
}
- if (callback) {
- callback(popup);
+ }),
+ apiCallback({
+ type: "menuItemClicked",
+ typeChecker: isMenuItemClickedEvent,
+ callback: event => {
+ const callback = menuCallbacks.get(event.menuItem);
+ if (callback) {
+ callback(event.menuItem)
+ }
}
- }
- }),
- apiCallback({
- type: "menuItemClicked",
- typeChecker: isMenuItemClickedEvent,
- callback: event => {
- const callback = menuCallbacks.get(event.menuItem);
- if (callback) {
- callback(event.menuItem)
+ }),
+ apiCallback({
+ type: "messageTriggered",
+ typeChecker: isMessageReferenceEvent,
+ callback: event => {
+ triggerMessageInstance?.trigger();
}
- }
- })];
+ })
+ ];
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
@@ -101,6 +102,9 @@ class WorkAdventureUiCommands extends IframeApiContribution void): TriggerMessage {
+ return new TriggerMessage(message, callback);
+ }
}
export default new WorkAdventureUiCommands();
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index 7c07f187..2cb4a363 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -64,7 +64,8 @@ import type {
ITiledMapLayerProperty,
ITiledMapObject,
ITiledMapTileLayer,
- ITiledTileSet } from "../Map/ITiledMap";
+ ITiledTileSet
+} from "../Map/ITiledMap";
import { MenuScene, MenuSceneName } from '../Menu/MenuScene';
import { PlayerAnimationDirections } from "../Player/Animation";
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
@@ -93,7 +94,8 @@ import Tilemap = Phaser.Tilemaps.Tilemap;
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
import AnimatedTiles from "phaser-animated-tiles";
-import {soundManager} from "./SoundManager";
+import { soundManager } from "./SoundManager";
+import { removeTriggerMessageEvent, sendMessageTriggeredEvent, triggerMessageEvent } from '../../Api/Events/ui/TriggerMessageEventHandler';
export interface GameSceneInitInterface {
initPosition: PointInterface | null,
@@ -932,11 +934,11 @@ ${escapedMessage}
scriptedBubbleSprite.destroy();
}));
- this.iframeSubscriptionList.push(iframeListener.showLayerStream.subscribe((layerEvent)=>{
+ this.iframeSubscriptionList.push(iframeListener.showLayerStream.subscribe((layerEvent) => {
this.setLayerVisibility(layerEvent.name, true);
}));
- this.iframeSubscriptionList.push(iframeListener.hideLayerStream.subscribe((layerEvent)=>{
+ this.iframeSubscriptionList.push(iframeListener.hideLayerStream.subscribe((layerEvent) => {
this.setLayerVisibility(layerEvent.name, false);
}));
@@ -945,7 +947,7 @@ ${escapedMessage}
}));
this.iframeSubscriptionList.push(iframeListener.dataLayerChangeStream.subscribe(() => {
- iframeListener.sendDataLayerEvent({data: this.gameMap.getMap()});
+ iframeListener.sendDataLayerEvent({ data: this.gameMap.getMap() });
}))
this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => {
@@ -959,21 +961,33 @@ ${escapedMessage}
})
}));
+
+ this.iframeSubscriptionList.push(triggerMessageEvent.subscribe(message => {
+ layoutManager.addActionButton(message.uuid, message.message, () => {
+ sendMessageTriggeredEvent(message.uuid)
+ layoutManager.removeActionButton(message.uuid, this.userInputManager);
+ }, this.userInputManager);
+ }))
+
+ this.iframeSubscriptionList.push(removeTriggerMessageEvent.subscribe(message => {
+ layoutManager.removeActionButton(message.uuid, this.userInputManager);
+ }))
+
}
private setPropertyLayer(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
const layer = this.gameMap.findLayer(layerName);
- if (layer === undefined) {
+ if (layer === undefined) {
console.warn('Could not find layer "' + layerName + '" when calling setProperty');
return;
}
- const property = (layer.properties as ITiledMapLayerProperty[])?.find((property) => property.name === propertyName);
- if (property === undefined) {
- layer.properties = [];
- layer.properties.push({name : propertyName, type : typeof propertyValue, value : propertyValue});
- return;
- }
- property.value = propertyValue;
+ const property = (layer.properties as ITiledMapLayerProperty[])?.find((property) => property.name === propertyName);
+ if (property === undefined) {
+ layer.properties = [];
+ layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
+ return;
+ }
+ property.value = propertyValue;
}
private setLayerVisibility(layerName: string, visible: boolean): void {
@@ -1150,7 +1164,7 @@ ${escapedMessage}
}
//todo: push that into the gameManager
- private loadNextGame(exitSceneIdentifier: string) : Promise{
+ private loadNextGame(exitSceneIdentifier: string): Promise {
const { roomId, hash } = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
const room = new Room(roomId);
return gameManager.loadMap(room, this.scene).catch(() => { });
@@ -1197,7 +1211,7 @@ ${escapedMessage}
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
});
- phaserLayer.setCollisionByProperty({collides: true});
+ phaserLayer.setCollisionByProperty({ collides: true });
if (DEBUG_MODE) {
//debug code to see the collision hitbox of the object in the top layer
phaserLayer.renderDebug(this.add.graphics(), {
@@ -1206,7 +1220,7 @@ ${escapedMessage}
faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges
});
}
- //});
+ //});
}
}
}
diff --git a/maps/tests/script.js b/maps/tests/script.js
index b300700f..ac3541f6 100644
--- a/maps/tests/script.js
+++ b/maps/tests/script.js
@@ -1,40 +1,41 @@
+///
console.log('SCRIPT LAUNCHED');
//WA.sendChatMessage('Hi, my name is Poly and I repeat what you say!', 'Poly Parrot');
var isFirstTimeTuto = false;
var textFirstPopup = 'Hey ! This is how to open start a discussion with someone ! You can be 4 max in a booble';
var textSecondPopup = 'You can also use the chat to communicate ! ';
-var targetObjectTutoBubble ='myPopup1';
-var targetObjectTutoChat ='myPopup2';
+var targetObjectTutoBubble = 'myPopup1';
+var targetObjectTutoChat = 'myPopup2';
var popUpExplanation = undefined;
-function launchTuto (){
- WA.ui.openPopup(targetObjectTutoBubble, textFirstPopup, [
- {
- label: "Next",
- className: "popUpElement",
- callback: (popup) => {
- popup.close();
+function launchTuto() {
+ WA.ui.openPopup(targetObjectTutoBubble, textFirstPopup, [
+ {
+ label: "Next",
+ className: "popUpElement",
+ callback: (popup) => {
+ popup.close();
- WA.ui.openPopup(targetObjectTutoChat, textSecondPopup, [
- {
- label: "Open Chat",
- className: "popUpElement",
- callback: (popup1) => {
- WA.chat.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
- popup1.close();
- WA.controls.restorePlayerControls();
- }
+ WA.ui.openPopup(targetObjectTutoChat, textSecondPopup, [
+ {
+ label: "Open Chat",
+ className: "popUpElement",
+ callback: (popup1) => {
+ WA.chat.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
+ popup1.close();
+ WA.controls.restorePlayerControls();
}
+ }
- ])
- }
+ ])
}
- ]);
- WA.controls.disablePlayerControls();
+ }
+ ]);
+ WA.controls.disablePlayerControls();
}
WA.chat.onChatMessage((message => {
console.log('CHAT MESSAGE RECEIVED BY SCRIPT');
- WA.chat.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot');
+ WA.chat.sendChatMessage('Poly Parrot says: "' + message + '"', 'Poly Parrot');
}));
WA.room.onEnterZone('myTrigger', () => {
@@ -50,11 +51,11 @@ WA.room.onEnterZone('notExist', () => {
WA.room.onEnterZone('popupZone', () => {
WA.ui.displayBubble();
- if (!isFirstTimeTuto) {
+ if(!isFirstTimeTuto) {
isFirstTimeTuto = true;
launchTuto();
}
- else popUpExplanation = WA.ui.openPopup(targetObjectTutoChat,'Do you want to review the explanation ? ', [
+ else popUpExplanation = WA.ui.openPopup(targetObjectTutoChat, 'Do you want to review the explanation ? ', [
{
label: "No",
className: "popUpElementReviewexplanation",
@@ -74,6 +75,13 @@ WA.room.onEnterZone('popupZone', () => {
});
WA.room.onLeaveZone('popupZone', () => {
- if (popUpExplanation !== undefined) popUpExplanation.close();
+ if(popUpExplanation !== undefined) popUpExplanation.close();
WA.ui.removeBubble();
})
+
+const message = WA.ui.triggerMessage("testMessage", () => {
+ WA.chat.sendChatMessage("triggered", "triggerbot");
+})
+setTimeout(() => {
+ message.remove();
+}, 5000)
\ No newline at end of file