Merge pull request #790 from thecodingmachine/iframe_api
Adding an API for inter-iframe communication
@ -88,8 +88,6 @@
|
||||
"JITSI_URL": env.JITSI_URL,
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
||||
"TURN_USER": "workadventure",
|
||||
"TURN_PASSWORD": "WorkAdventure123",
|
||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
||||
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
|
||||
//"GA_TRACKING_ID": "UA-10196481-11"
|
||||
|
@ -43,7 +43,7 @@ services:
|
||||
- ./front:/usr/src/app
|
||||
labels:
|
||||
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
||||
- "traefik.http.routers.front.entryPoints=web,traefik"
|
||||
- "traefik.http.routers.front.entryPoints=web"
|
||||
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.front-ssl.rule=Host(`play.workadventure.localhost`)"
|
||||
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||
|
@ -8,6 +8,11 @@ FROM thecodingmachine/nodejs:14-apache
|
||||
|
||||
COPY --chown=docker:docker front .
|
||||
COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
|
||||
|
||||
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
||||
# iframe.html is only in dev mode to circumvent a limitation
|
||||
RUN rm dist/iframe.html
|
||||
|
||||
RUN yarn install
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
3
front/dist/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
index.html
|
||||
index.tmpl.html.tmp
|
||||
style.*.css
|
||||
/js/
|
||||
style.*.css
|
||||
|
17
front/dist/iframe.html
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/iframe_api.js" ></script>
|
||||
<script>
|
||||
// Note: this is a huge XSS flow as we allow anyone to load a Javascript file in our domain.
|
||||
// This file must ABSOLUTELY be removed from the Docker images/deployments and is only here
|
||||
// for development purpose (because dynamically generated iframes are not working with
|
||||
// webpack hot reload due to an issue with rights)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const scriptUrl = urlParams.get('script');
|
||||
const script = document.createElement('script');
|
||||
script.src = scriptUrl;
|
||||
document.head.append(script);
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
3
front/dist/index.tmpl.html
vendored
@ -29,6 +29,9 @@
|
||||
|
||||
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||
|
||||
<title>WorkAdventure</title>
|
||||
</head>
|
||||
<body id="body" style="margin: 0; background-color: #000">
|
||||
|
25
front/dist/resources/style/style.css
vendored
@ -1123,6 +1123,31 @@ div.action p.action-body{
|
||||
margin-left: calc(50% - 75px);
|
||||
border-radius: 15px;
|
||||
}
|
||||
.popUpElement{
|
||||
font-family: 'Press Start 2P';
|
||||
text-align: left;
|
||||
color: white;
|
||||
}
|
||||
.popUpElement div {
|
||||
font-family: 'Press Start 2P';
|
||||
font-size: 10px;
|
||||
background-color: #727678;
|
||||
}
|
||||
|
||||
.popUpElement button {
|
||||
position: relative;
|
||||
font-size: 10px;
|
||||
border-image-repeat: revert;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.popUpElement .buttonContainer {
|
||||
float: right;
|
||||
background-color: inherit;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@keyframes mymove {
|
||||
0% {bottom: 40px;}
|
||||
50% {bottom: 30px;}
|
||||
|
@ -336,7 +336,7 @@ export class ConsoleGlobalMessageManager {
|
||||
}
|
||||
|
||||
active(){
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.userInputManager.disableControls();
|
||||
this.divMainConsole.style.top = '0';
|
||||
this.activeConsole = true;
|
||||
}
|
||||
|
11
front/src/Api/Events/ButtonClickedEvent.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isButtonClickedEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
popupId: tg.isNumber,
|
||||
buttonId: tg.isNumber,
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||
*/
|
||||
export type ButtonClickedEvent = tg.GuardedType<typeof isButtonClickedEvent>;
|
11
front/src/Api/Events/ChatEvent.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isChatEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
message: tg.isString,
|
||||
author: tg.isString,
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type ChatEvent = tg.GuardedType<typeof isChatEvent>;
|
11
front/src/Api/Events/ClosePopupEvent.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isClosePopupEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
popupId: tg.isNumber,
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type ClosePopupEvent = tg.GuardedType<typeof isClosePopupEvent>;
|
10
front/src/Api/Events/EnterLeaveEvent.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isEnterLeaveEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
name: tg.isString,
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property.
|
||||
*/
|
||||
export type EnterLeaveEvent = tg.GuardedType<typeof isEnterLeaveEvent>;
|
13
front/src/Api/Events/GoToPageEvent.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isGoToPageEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type GoToPageEvent = tg.GuardedType<typeof isGoToPageEvent>;
|
7
front/src/Api/Events/IframeEvent.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface IframeEvent {
|
||||
type: string;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string';
|
13
front/src/Api/Events/OpenCoWebSiteEvent.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isOpenCoWebsite =
|
||||
new tg.IsInterface().withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type OpenCoWebSiteEvent = tg.GuardedType<typeof isOpenCoWebsite>;
|
20
front/src/Api/Events/OpenPopupEvent.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
const isButtonDescriptor =
|
||||
new tg.IsInterface().withProperties({
|
||||
label: tg.isString,
|
||||
className: tg.isOptional(tg.isString)
|
||||
}).get();
|
||||
|
||||
export const isOpenPopupEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
popupId: tg.isNumber,
|
||||
targetObject: tg.isString,
|
||||
message: tg.isString,
|
||||
buttons: tg.isArray(isButtonDescriptor)
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type OpenPopupEvent = tg.GuardedType<typeof isOpenPopupEvent>;
|
13
front/src/Api/Events/OpenTabEvent.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isOpenTabEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type OpenTabEvent = tg.GuardedType<typeof isOpenTabEvent>;
|
10
front/src/Api/Events/UserInputChatEvent.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isUserInputChatEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
message: tg.isString,
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the game to the iFrame when a user types a message in the chat.
|
||||
*/
|
||||
export type UserInputChatEvent = tg.GuardedType<typeof isUserInputChatEvent>;
|
238
front/src/Api/IframeListener.ts
Normal file
@ -0,0 +1,238 @@
|
||||
import {Subject} from "rxjs";
|
||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
||||
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
|
||||
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
||||
import * as crypto from "crypto";
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
||||
import {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";
|
||||
|
||||
|
||||
/**
|
||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||
* Also allows to send messages to those iframes.
|
||||
*/
|
||||
class IframeListener {
|
||||
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||
public readonly chatStream = this._chatStream.asObservable();
|
||||
|
||||
private readonly _openPopupStream: Subject<OpenPopupEvent> = new Subject();
|
||||
public readonly openPopupStream = this._openPopupStream.asObservable();
|
||||
|
||||
private readonly _openTabStream: Subject<OpenTabEvent> = new Subject();
|
||||
public readonly openTabStream = this._openTabStream.asObservable();
|
||||
|
||||
private readonly _goToPageStream: Subject<GoToPageEvent> = new Subject();
|
||||
public readonly goToPageStream = this._goToPageStream.asObservable();
|
||||
|
||||
private readonly _openCoWebSiteStream: Subject<OpenCoWebSiteEvent> = new Subject();
|
||||
public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
|
||||
|
||||
private readonly _closeCoWebSiteStream: Subject<void> = new Subject();
|
||||
public readonly closeCoWebSiteStream = this._closeCoWebSiteStream.asObservable();
|
||||
|
||||
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
||||
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||
|
||||
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
||||
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
||||
|
||||
private readonly _closePopupStream: Subject<ClosePopupEvent> = new Subject();
|
||||
public readonly closePopupStream = this._closePopupStream.asObservable();
|
||||
|
||||
private readonly _displayBubbleStream: Subject<void> = new Subject();
|
||||
public readonly displayBubbleStream = this._displayBubbleStream.asObservable();
|
||||
|
||||
private readonly _removeBubbleStream: Subject<void> = new Subject();
|
||||
public readonly removeBubbleStream = this._removeBubbleStream.asObservable();
|
||||
|
||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||
|
||||
init() {
|
||||
window.addEventListener("message", (message) => {
|
||||
// Do we trust the sender of this message?
|
||||
// Let's only accept messages from the iframe that are allowed.
|
||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||
let found = false;
|
||||
for (const iframe of this.iframes) {
|
||||
if (iframe.contentWindow === message.source) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = message.data;
|
||||
if (isIframeEventWrapper(payload)) {
|
||||
if (payload.type === 'chat' && isChatEvent(payload.data)) {
|
||||
this._chatStream.next(payload.data);
|
||||
} else if (payload.type === 'openPopup' && isOpenPopupEvent(payload.data)) {
|
||||
this._openPopupStream.next(payload.data);
|
||||
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
||||
this._closePopupStream.next(payload.data);
|
||||
}
|
||||
else if(payload.type === 'openTab' && isOpenTabEvent(payload.data)) {
|
||||
scriptUtils.openTab(payload.data.url);
|
||||
}
|
||||
else if(payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||
scriptUtils.goToPage(payload.data.url);
|
||||
}
|
||||
else if(payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||
scriptUtils.openCoWebsite(payload.data.url);
|
||||
}
|
||||
else if(payload.type === 'closeCoWebSite') {
|
||||
scriptUtils.closeCoWebSite();
|
||||
}
|
||||
else if (payload.type === 'disablePlayerControl'){
|
||||
this._disablePlayerControlStream.next();
|
||||
}
|
||||
else if (payload.type === 'restorePlayerControl'){
|
||||
this._enablePlayerControlStream.next();
|
||||
}
|
||||
else if (payload.type === 'displayBubble'){
|
||||
this._displayBubbleStream.next();
|
||||
}
|
||||
else if (payload.type === 'removeBubble'){
|
||||
this._removeBubbleStream.next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}, false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the passed iFrame to send/receive messages via the API.
|
||||
*/
|
||||
registerIframe(iframe: HTMLIFrameElement): void {
|
||||
this.iframes.add(iframe);
|
||||
}
|
||||
|
||||
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||
this.iframes.delete(iframe);
|
||||
}
|
||||
|
||||
registerScript(scriptUrl: string): void {
|
||||
console.log('Loading map related script at ', scriptUrl)
|
||||
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||
// Using external iframe mode (
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = '/iframe.html?script='+encodeURIComponent(scriptUrl);
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add('allow-scripts');
|
||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||
|
||||
document.body.prepend(iframe);
|
||||
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
this.registerIframe(iframe);
|
||||
} else {
|
||||
// production code
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add('allow-scripts');
|
||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||
|
||||
const html = '<!doctype html>\n' +
|
||||
'\n' +
|
||||
'<html lang="en">\n' +
|
||||
'<head>\n' +
|
||||
'<script src="'+window.location.protocol+'//'+window.location.host+'/iframe_api.js" ></script>\n' +
|
||||
'<script src="'+scriptUrl+'" ></script>\n' +
|
||||
'</head>\n' +
|
||||
'</html>\n';
|
||||
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc = html;
|
||||
|
||||
document.body.prepend(iframe);
|
||||
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
this.registerIframe(iframe);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private getIFrameId(scriptUrl: string): string {
|
||||
return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex");
|
||||
}
|
||||
|
||||
unregisterScript(scriptUrl: string): void {
|
||||
const iFrameId = this.getIFrameId(scriptUrl);
|
||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||
if (!iframe) {
|
||||
throw new Error('Unknown iframe for script "'+scriptUrl+'"');
|
||||
}
|
||||
this.unregisterIframe(iframe);
|
||||
iframe.remove();
|
||||
|
||||
this.scripts.delete(scriptUrl);
|
||||
}
|
||||
|
||||
sendUserInputChat(message: string) {
|
||||
this.postMessage({
|
||||
'type': 'userInputChat',
|
||||
'data': {
|
||||
'message': message,
|
||||
} as UserInputChatEvent
|
||||
});
|
||||
}
|
||||
|
||||
sendEnterEvent(name: string) {
|
||||
this.postMessage({
|
||||
'type': 'enterEvent',
|
||||
'data': {
|
||||
"name": name
|
||||
} as EnterLeaveEvent
|
||||
});
|
||||
}
|
||||
|
||||
sendLeaveEvent(name: string) {
|
||||
this.postMessage({
|
||||
'type': 'leaveEvent',
|
||||
'data': {
|
||||
"name": name
|
||||
} as EnterLeaveEvent
|
||||
});
|
||||
}
|
||||
|
||||
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
||||
this.postMessage({
|
||||
'type': 'buttonClickedEvent',
|
||||
'data': {
|
||||
popupId,
|
||||
buttonId
|
||||
} as ButtonClickedEvent
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the message... to all allowed iframes.
|
||||
*/
|
||||
private postMessage(message: IframeEvent) {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.contentWindow?.postMessage(message, '*');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const iframeListener = new IframeListener();
|
23
front/src/Api/ScriptUtils.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import {coWebsiteManager} from "../WebRtc/CoWebsiteManager";
|
||||
|
||||
class ScriptUtils {
|
||||
|
||||
public openTab(url : string){
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
public goToPage(url : string){
|
||||
window.location.href = url;
|
||||
|
||||
}
|
||||
|
||||
public openCoWebsite(url : string){
|
||||
coWebsiteManager.loadCoWebsite(url,url);
|
||||
}
|
||||
|
||||
public closeCoWebSite(){
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
}
|
||||
}
|
||||
|
||||
export const scriptUtils = new ScriptUtils();
|
@ -59,11 +59,14 @@ import {TextureError} from "../../Exception/TextureError";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {iframeListener} from "../../Api/IframeListener";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import {Subscription} from "rxjs";
|
||||
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
|
||||
|
||||
@ -157,6 +160,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
private playerName!: string;
|
||||
private characterLayers!: string[];
|
||||
private messageSubscription: Subscription|null = null;
|
||||
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
||||
super({
|
||||
@ -263,7 +267,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported object type: "'+ itemType +'"');
|
||||
continue;
|
||||
//throw new Error('Unsupported object type: "'+ itemType +'"');
|
||||
}
|
||||
|
||||
itemFactory.preload(this.load);
|
||||
@ -289,6 +294,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Now, let's load the script, if any
|
||||
const scripts = this.getScriptUrls(this.mapFile);
|
||||
for (const script of scripts) {
|
||||
iframeListener.registerScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
//hook initialisation
|
||||
@ -306,7 +317,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
gameManager.gameSceneIsCreated(this);
|
||||
urlManager.pushRoomIdToUrl(this.room);
|
||||
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||
|
||||
|
||||
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
|
||||
|
||||
const playerName = gameManager.getPlayerName();
|
||||
@ -410,6 +421,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
// From now, this game scene will be notified of reposition events
|
||||
layoutManager.setListener(this);
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
this.listenToIframeEvents();
|
||||
|
||||
const camera = this.cameras.main;
|
||||
|
||||
@ -577,12 +589,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
const contextRed = this.circleRedTexture.context;
|
||||
contextRed.beginPath();
|
||||
contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false);
|
||||
// context.lineWidth = 5;
|
||||
//context.lineWidth = 5;
|
||||
contextRed.strokeStyle = '#ff0000';
|
||||
contextRed.stroke();
|
||||
this.circleRedTexture.refresh();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) {
|
||||
try {
|
||||
@ -606,7 +618,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
}else{
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined);
|
||||
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined);
|
||||
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||
};
|
||||
|
||||
@ -672,6 +684,103 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange('zone', (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === '') {
|
||||
iframeListener.sendLeaveEvent(oldValue as string);
|
||||
|
||||
} else {
|
||||
iframeListener.sendEnterEvent(newValue as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private listenToIframeEvents(): void {
|
||||
iframeListener.openPopupStream.subscribe((openPopupEvent) => {
|
||||
|
||||
let objectLayerSquare : ITiledMapObject;
|
||||
const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject);
|
||||
if (targetObjectData !== undefined){
|
||||
objectLayerSquare = targetObjectData;
|
||||
} else {
|
||||
console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map.");
|
||||
return;
|
||||
}
|
||||
const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message);
|
||||
let html = `<div id="container"><div class="nes-container with-title is-centered">
|
||||
${escapedMessage}
|
||||
</div> </div>`;
|
||||
const buttonContainer = `<div class="buttonContainer"</div>`;
|
||||
html += buttonContainer;
|
||||
let id = 0;
|
||||
for (const button of openPopupEvent.buttons) {
|
||||
html += `<button type="button" class="nes-btn is-${HtmlUtils.escapeHtml(button.className ?? '')}" id="popup-${openPopupEvent.popupId}-${id}">${HtmlUtils.escapeHtml(button.label)}</button>`;
|
||||
id++;
|
||||
}
|
||||
const domElement = this.add.dom(objectLayerSquare.x + objectLayerSquare.width/2 ,
|
||||
objectLayerSquare.y + objectLayerSquare.height/2).createFromHTML(html);
|
||||
|
||||
const container : HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement;
|
||||
container.style.width = objectLayerSquare.width + "px";
|
||||
domElement.scale = 0;
|
||||
domElement.setClassName('popUpElement');
|
||||
|
||||
|
||||
|
||||
id = 0;
|
||||
for (const button of openPopupEvent.buttons) {
|
||||
const button = HtmlUtils.getElementByIdOrFail<HTMLButtonElement>(`popup-${openPopupEvent.popupId}-${id}`);
|
||||
const btnId = id;
|
||||
button.onclick = () => {
|
||||
iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
|
||||
this.tweens.add({
|
||||
targets : domElement ,
|
||||
scale : 1,
|
||||
ease : "EaseOut",
|
||||
duration : 400,
|
||||
});
|
||||
|
||||
this.popUpElements.set(openPopupEvent.popupId, domElement);
|
||||
});
|
||||
|
||||
iframeListener.closePopupStream.subscribe((closePopupEvent) => {
|
||||
const popUpElement = this.popUpElements.get(closePopupEvent.popupId);
|
||||
if (popUpElement === undefined) {
|
||||
console.error('Could not close popup with ID ', closePopupEvent.popupId,'. Maybe it has already been closed?');
|
||||
}
|
||||
|
||||
this.tweens.add({
|
||||
targets : popUpElement ,
|
||||
scale : 0,
|
||||
ease : "EaseOut",
|
||||
duration : 400,
|
||||
onComplete : () => {
|
||||
popUpElement?.destroy();
|
||||
this.popUpElements.delete(closePopupEvent.popupId);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
iframeListener.disablePlayerControlStream.subscribe(()=>{
|
||||
this.userInputManager.disableControls();
|
||||
})
|
||||
iframeListener.enablePlayerControlStream.subscribe(()=>{
|
||||
this.userInputManager.restoreControls();
|
||||
})
|
||||
let scriptedBubbleSprite : Sprite;
|
||||
iframeListener.displayBubbleStream.subscribe(()=>{
|
||||
scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white');
|
||||
scriptedBubbleSprite.setDisplayOrigin(48, 48);
|
||||
this.add.existing(scriptedBubbleSprite);
|
||||
})
|
||||
iframeListener.removeBubbleStream.subscribe(()=>{
|
||||
scriptedBubbleSprite.destroy();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private getMapDirUrl(): string {
|
||||
@ -702,6 +811,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
public cleanupClosingScene(): void {
|
||||
// stop playing audio, close any open website, stop any open Jitsi
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
// Stop the script, if any
|
||||
const scripts = this.getScriptUrls(this.mapFile);
|
||||
for (const script of scripts) {
|
||||
iframeListener.unregisterScript(script);
|
||||
}
|
||||
|
||||
this.stopJitsi();
|
||||
audioManager.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
@ -785,8 +900,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
return this.getProperty(layer, "startLayer") == true;
|
||||
}
|
||||
|
||||
private getProperty(layer: ITiledMapLayer, name: string): string|boolean|number|undefined {
|
||||
const properties = layer.properties;
|
||||
private getScriptUrls(map: ITiledMap): string[] {
|
||||
return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString());
|
||||
}
|
||||
|
||||
private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined {
|
||||
const properties: ITiledMapLayerProperty[] = layer.properties;
|
||||
if (!properties) {
|
||||
return undefined;
|
||||
}
|
||||
@ -797,6 +916,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
return obj.value;
|
||||
}
|
||||
|
||||
private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] {
|
||||
const properties: ITiledMapLayerProperty[] = layer.properties;
|
||||
if (!properties) {
|
||||
return [];
|
||||
}
|
||||
return properties.filter((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase()).map((property) => property.value);
|
||||
}
|
||||
|
||||
//todo: push that into the gameManager
|
||||
private async loadNextGame(exitSceneIdentifier: string){
|
||||
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
||||
@ -1176,7 +1303,19 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
bottom: camera.scrollY + camera.height,
|
||||
});
|
||||
}
|
||||
private getObjectLayerData(objectName : string) : ITiledMapObject| undefined{
|
||||
for (const layer of this.mapFile.layers) {
|
||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||
for (const object of layer.objects) {
|
||||
if (object.name === objectName) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
}
|
||||
private reposition(): void {
|
||||
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||
this.chatModeSprite.setY(this.game.renderer.height - 2);
|
||||
@ -1233,7 +1372,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private bannedUser(){
|
||||
this.cleanupClosingScene();
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.userInputManager.disableControls();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Banned',
|
||||
subTitle: 'You were banned from WorkAdventure',
|
||||
@ -1245,7 +1384,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
private showWorldFullError(): void {
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop(ReconnectingSceneName);
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.userInputManager.disableControls();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Connection rejected',
|
||||
subTitle: 'The world you are trying to join is full. Try again later.',
|
||||
|
@ -14,7 +14,7 @@ export interface ITiledMap {
|
||||
* Map orientation (orthogonal)
|
||||
*/
|
||||
orientation: string;
|
||||
properties: {[key: string]: string};
|
||||
properties: ITiledMapLayerProperty[];
|
||||
|
||||
/**
|
||||
* Render order (right-down)
|
||||
|
@ -61,7 +61,7 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
||||
|
||||
this.opened = true;
|
||||
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys();
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.disableControls();
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
|
@ -31,10 +31,11 @@ export class ActiveEventList {
|
||||
export class UserInputManager {
|
||||
private KeysCode!: UserInputManagerDatum[];
|
||||
private Scene: GameScene;
|
||||
|
||||
private isInputDisabled : boolean;
|
||||
constructor(Scene : GameScene) {
|
||||
this.Scene = Scene;
|
||||
this.initKeyBoardEvent();
|
||||
this.isInputDisabled = false;
|
||||
}
|
||||
|
||||
initKeyBoardEvent(){
|
||||
@ -63,16 +64,25 @@ export class UserInputManager {
|
||||
this.Scene.input.keyboard.removeAllListeners();
|
||||
}
|
||||
|
||||
clearAllKeys(){
|
||||
disableControls(){
|
||||
this.Scene.input.keyboard.removeAllKeys();
|
||||
this.isInputDisabled = true;
|
||||
}
|
||||
|
||||
restoreControls(){
|
||||
this.initKeyBoardEvent();
|
||||
this.isInputDisabled = false;
|
||||
}
|
||||
getEventListForGameTick(): ActiveEventList {
|
||||
const eventsMap = new ActiveEventList();
|
||||
if (this.isInputDisabled) {
|
||||
return eventsMap;
|
||||
}
|
||||
this.KeysCode.forEach(d => {
|
||||
if (d. keyInstance.isDown) {
|
||||
eventsMap.set(d.event, true);
|
||||
}
|
||||
|
||||
});
|
||||
return eventsMap;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {Subject} from "rxjs";
|
||||
import {iframeListener} from "../Api/IframeListener";
|
||||
|
||||
enum iframeStates {
|
||||
closed = 1,
|
||||
@ -17,8 +18,8 @@ const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
|
||||
const animationTime = 500; //time used by the css transitions, in ms.
|
||||
|
||||
class CoWebsiteManager {
|
||||
|
||||
private opened: iframeStates = iframeStates.closed;
|
||||
|
||||
private opened: iframeStates = iframeStates.closed;
|
||||
|
||||
private _onResize: Subject<void> = new Subject();
|
||||
public onResize = this._onResize.asObservable();
|
||||
@ -27,11 +28,11 @@ class CoWebsiteManager {
|
||||
* So we use this promise to queue up every cowebsite state transition
|
||||
*/
|
||||
private currentOperationPromise: Promise<void> = Promise.resolve();
|
||||
private cowebsiteDiv: HTMLDivElement;
|
||||
private cowebsiteDiv: HTMLDivElement;
|
||||
private resizing: boolean = false;
|
||||
private cowebsiteMainDom: HTMLDivElement;
|
||||
private cowebsiteAsideDom: HTMLDivElement;
|
||||
|
||||
|
||||
get width(): number {
|
||||
return this.cowebsiteDiv.clientWidth;
|
||||
}
|
||||
@ -47,15 +48,15 @@ class CoWebsiteManager {
|
||||
set height(height: number) {
|
||||
this.cowebsiteDiv.style.height = height+'px';
|
||||
}
|
||||
|
||||
|
||||
get verticalMode(): boolean {
|
||||
return window.innerWidth < window.innerHeight;
|
||||
}
|
||||
|
||||
|
||||
get isFullScreen(): boolean {
|
||||
return this.verticalMode ? this.height === window.innerHeight : this.width === window.innerWidth;
|
||||
}
|
||||
|
||||
|
||||
constructor() {
|
||||
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
|
||||
@ -76,7 +77,7 @@ class CoWebsiteManager {
|
||||
this.verticalMode ? this.height -= event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
|
||||
this.fire();
|
||||
}
|
||||
|
||||
|
||||
this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
|
||||
this.resizing = true;
|
||||
this.getIframeDom().style.display = 'none';
|
||||
@ -91,7 +92,7 @@ class CoWebsiteManager {
|
||||
this.resizing = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private getDevicePixelRatio(): number {
|
||||
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
|
||||
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
|
||||
@ -126,7 +127,7 @@ class CoWebsiteManager {
|
||||
return iframe;
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string, base: string, allowPolicy?: string): void {
|
||||
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void {
|
||||
this.load();
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
|
||||
@ -134,11 +135,14 @@ class CoWebsiteManager {
|
||||
iframe.id = 'cowebsite-iframe';
|
||||
iframe.src = (new URL(url, base)).toString();
|
||||
if (allowPolicy) {
|
||||
iframe.allow = allowPolicy;
|
||||
iframe.allow = allowPolicy;
|
||||
}
|
||||
const onloadPromise = new Promise((resolve) => {
|
||||
iframe.onload = () => resolve();
|
||||
});
|
||||
if (allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
this.cowebsiteMainDom.appendChild(iframe);
|
||||
const onTimeoutPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
@ -170,6 +174,10 @@ class CoWebsiteManager {
|
||||
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
|
||||
this.close();
|
||||
this.fire();
|
||||
const iframe = this.cowebsiteDiv.querySelector('iframe');
|
||||
if (iframe) {
|
||||
iframeListener.unregisterIframe(iframe);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
resolve();
|
||||
@ -197,11 +205,11 @@ class CoWebsiteManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fire(): void {
|
||||
this._onResize.next();
|
||||
}
|
||||
|
||||
|
||||
private fullscreen(): void {
|
||||
if (this.isFullScreen) {
|
||||
this.resetStyle();
|
||||
|
@ -3,6 +3,7 @@ import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
import {iframeListener} from "../Api/IframeListener";
|
||||
|
||||
export type SendMessageCallback = (message:string) => void;
|
||||
|
||||
@ -25,6 +26,14 @@ export class DiscussionManager {
|
||||
constructor() {
|
||||
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
this.createDiscussPart(''); //todo: why do we always use empty string?
|
||||
|
||||
iframeListener.chatStream.subscribe((chatEvent) => {
|
||||
this.addMessage(chatEvent.author, chatEvent.message, false);
|
||||
this.showDiscussion();
|
||||
});
|
||||
this.onSendMessageCallback('iframe_listener', (message) => {
|
||||
iframeListener.sendUserInputChat(message);
|
||||
})
|
||||
}
|
||||
|
||||
private createDiscussPart(name: string) {
|
||||
@ -61,12 +70,12 @@ export class DiscussionManager {
|
||||
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||
inputMessage.onfocus = () => {
|
||||
if(this.userInputManager) {
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.userInputManager.disableControls();
|
||||
}
|
||||
}
|
||||
inputMessage.onblur = () => {
|
||||
if(this.userInputManager) {
|
||||
this.userInputManager.initKeyBoardEvent();
|
||||
this.userInputManager.restoreControls();
|
||||
}
|
||||
}
|
||||
inputMessage.type = "text";
|
||||
|
@ -24,7 +24,7 @@ export class HtmlUtils {
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
}
|
||||
|
||||
private static escapeHtml(html: string): string {
|
||||
public static escapeHtml(html: string): string {
|
||||
const text = document.createTextNode(html);
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(text);
|
||||
|
232
front/src/iframe_api.ts
Normal file
@ -0,0 +1,232 @@
|
||||
import {ChatEvent, isChatEvent} from "./Api/Events/ChatEvent";
|
||||
import {isIframeEventWrapper} from "./Api/Events/IframeEvent";
|
||||
import {isUserInputChatEvent, UserInputChatEvent} from "./Api/Events/UserInputChatEvent";
|
||||
import {Subject} from "rxjs";
|
||||
import {EnterLeaveEvent, isEnterLeaveEvent} from "./Api/Events/EnterLeaveEvent";
|
||||
import {OpenPopupEvent} from "./Api/Events/OpenPopupEvent";
|
||||
import {isButtonClickedEvent} from "./Api/Events/ButtonClickedEvent";
|
||||
import {ClosePopupEvent} from "./Api/Events/ClosePopupEvent";
|
||||
import {OpenTabEvent} from "./Api/Events/OpenTabEvent";
|
||||
import {GoToPageEvent} from "./Api/Events/GoToPageEvent";
|
||||
import {OpenCoWebSiteEvent} from "./Api/Events/OpenCoWebSiteEvent";
|
||||
|
||||
interface WorkAdventureApi {
|
||||
sendChatMessage(message: string, author: string): void;
|
||||
onChatMessage(callback: (message: string) => void): void;
|
||||
onEnterZone(name: string, callback: () => void): void;
|
||||
onLeaveZone(name: string, callback: () => void): void;
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup;
|
||||
openTab(url : string): void;
|
||||
goToPage(url : string): void;
|
||||
openCoWebSite(url : string): void;
|
||||
closeCoWebSite(): void;
|
||||
disablePlayerControl() : void;
|
||||
restorePlayerControl() : void;
|
||||
displayBubble() : void;
|
||||
removeBubble() : void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var WA: WorkAdventureApi
|
||||
|
||||
}
|
||||
|
||||
type ChatMessageCallback = (message: string) => void;
|
||||
type ButtonClickedCallback = (popup: Popup) => void;
|
||||
|
||||
const userInputChatStream: Subject<UserInputChatEvent> = new Subject();
|
||||
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>();
|
||||
|
||||
let popupId = 0;
|
||||
interface ButtonDescriptor {
|
||||
/**
|
||||
* The label of the button
|
||||
*/
|
||||
label: string,
|
||||
/**
|
||||
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
|
||||
*/
|
||||
className?: "normal"|"primary"|"success"|"warning"|"error"|"disabled",
|
||||
/**
|
||||
* Callback called if the button is pressed
|
||||
*/
|
||||
callback: ButtonClickedCallback,
|
||||
}
|
||||
|
||||
class Popup {
|
||||
constructor(private id: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the popup
|
||||
*/
|
||||
public close(): void {
|
||||
window.parent.postMessage({
|
||||
'type': 'closePopup',
|
||||
'data': {
|
||||
'popupId': this.id,
|
||||
} as ClosePopupEvent
|
||||
}, '*');
|
||||
}
|
||||
}
|
||||
|
||||
window.WA = {
|
||||
/**
|
||||
* Send a message in the chat.
|
||||
* Only the local user will receive this message.
|
||||
*/
|
||||
sendChatMessage(message: string, author: string) {
|
||||
window.parent.postMessage({
|
||||
'type': 'chat',
|
||||
'data': {
|
||||
'message': message,
|
||||
'author': author
|
||||
} as ChatEvent
|
||||
}, '*');
|
||||
},
|
||||
disablePlayerControl() : void {
|
||||
window.parent.postMessage({'type' : 'disablePlayerControl'},'*');
|
||||
},
|
||||
|
||||
restorePlayerControl() : void {
|
||||
window.parent.postMessage({'type' : 'restorePlayerControl'},'*');
|
||||
},
|
||||
|
||||
displayBubble() : void {
|
||||
window.parent.postMessage({'type' : 'displayBubble'},'*');
|
||||
},
|
||||
|
||||
removeBubble() : void {
|
||||
window.parent.postMessage({'type' : 'removeBubble'},'*');
|
||||
},
|
||||
|
||||
openTab(url : string) : void{
|
||||
window.parent.postMessage({
|
||||
"type" : 'openTab',
|
||||
"data" : {
|
||||
url
|
||||
} as OpenTabEvent
|
||||
},'*');
|
||||
},
|
||||
|
||||
goToPage(url : string) : void{
|
||||
window.parent.postMessage({
|
||||
"type" : 'goToPage',
|
||||
"data" : {
|
||||
url
|
||||
} as GoToPageEvent
|
||||
},'*');
|
||||
},
|
||||
|
||||
openCoWebSite(url : string) : void{
|
||||
window.parent.postMessage({
|
||||
"type" : 'openCoWebSite',
|
||||
"data" : {
|
||||
url
|
||||
} as OpenCoWebSiteEvent
|
||||
},'*');
|
||||
},
|
||||
|
||||
closeCoWebSite() : void{
|
||||
window.parent.postMessage({
|
||||
"type" : 'closeCoWebSite'
|
||||
},'*');
|
||||
},
|
||||
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||
popupId++;
|
||||
const popup = new Popup(popupId);
|
||||
const btnMap = new Map<number, () => void>();
|
||||
popupCallbacks.set(popupId, btnMap);
|
||||
let id = 0;
|
||||
for (const button of buttons) {
|
||||
const callback = button.callback;
|
||||
if (callback) {
|
||||
btnMap.set(id, () => {
|
||||
callback(popup);
|
||||
});
|
||||
}
|
||||
id++;
|
||||
}
|
||||
|
||||
|
||||
window.parent.postMessage({
|
||||
'type': 'openPopup',
|
||||
'data': {
|
||||
popupId,
|
||||
targetObject,
|
||||
message,
|
||||
buttons: buttons.map((button) => {
|
||||
return {
|
||||
label: button.label,
|
||||
className: button.className
|
||||
};
|
||||
})
|
||||
} as OpenPopupEvent
|
||||
}, '*');
|
||||
|
||||
popups.set(popupId, popup)
|
||||
return popup;
|
||||
},
|
||||
/**
|
||||
* Listen to messages sent by the local user, in the chat.
|
||||
*/
|
||||
onChatMessage(callback: ChatMessageCallback): void {
|
||||
userInputChatStream.subscribe((userInputChatEvent) => {
|
||||
callback(userInputChatEvent.message);
|
||||
});
|
||||
},
|
||||
onEnterZone(name: string, callback: () => void): void {
|
||||
let subject = enterStreams.get(name);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<EnterLeaveEvent>();
|
||||
enterStreams.set(name, subject);
|
||||
}
|
||||
subject.subscribe(callback);
|
||||
},
|
||||
onLeaveZone(name: string, callback: () => void): void {
|
||||
let subject = leaveStreams.get(name);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<EnterLeaveEvent>();
|
||||
leaveStreams.set(name, subject);
|
||||
}
|
||||
subject.subscribe(callback);
|
||||
},
|
||||
}
|
||||
|
||||
window.addEventListener('message', message => {
|
||||
if (message.source !== window.parent) {
|
||||
return; // Skip message in this event listener
|
||||
}
|
||||
|
||||
const payload = message.data;
|
||||
|
||||
console.log(payload);
|
||||
|
||||
if (isIframeEventWrapper(payload)) {
|
||||
const payloadData = payload.data;
|
||||
if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) {
|
||||
userInputChatStream.next(payloadData);
|
||||
} else if (payload.type === 'enterEvent' && isEnterLeaveEvent(payloadData)) {
|
||||
enterStreams.get(payloadData.name)?.next();
|
||||
} else if (payload.type === 'leaveEvent' && isEnterLeaveEvent(payloadData)) {
|
||||
leaveStreams.get(payloadData.name)?.next();
|
||||
} else if (payload.type === 'buttonClickedEvent' && isButtonClickedEvent(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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ...
|
||||
});
|
@ -15,6 +15,8 @@ import {MenuScene} from "./Phaser/Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
|
||||
import {localUserStore} from "./Connexion/LocalUserStore";
|
||||
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
|
||||
import {iframeListener} from "./Api/IframeListener";
|
||||
import {discussionManager} from "./WebRtc/DiscussionManager";
|
||||
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
|
||||
@ -119,3 +121,5 @@ coWebsiteManager.onResize.subscribe(() => {
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||
});
|
||||
|
||||
iframeListener.init();
|
||||
|
@ -4,7 +4,10 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.ts',
|
||||
entry: {
|
||||
'main': './src/index.ts',
|
||||
'iframe_api': './src/iframe_api.ts'
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
devServer: {
|
||||
contentBase: './dist',
|
||||
@ -34,7 +37,11 @@ module.exports = {
|
||||
extensions: [ '.tsx', '.ts', '.js' ],
|
||||
},
|
||||
output: {
|
||||
filename: '[name].[contenthash].js',
|
||||
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';
|
||||
},
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
publicPath: '/'
|
||||
},
|
||||
@ -54,7 +61,8 @@ module.exports = {
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
useShortDoctype: true
|
||||
}
|
||||
},
|
||||
chunks: ['main']
|
||||
}
|
||||
),
|
||||
new webpack.ProvidePlugin({
|
||||
|
25
maps/Tuto/Attribution-tilesets.txt
Normal file
@ -0,0 +1,25 @@
|
||||
License
|
||||
-------
|
||||
|
||||
CC-BY-SA 3.0:
|
||||
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||
- See the file: cc-by-sa-3.0.txt
|
||||
GNU GPL 3.0:
|
||||
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||
- See the file: gpl-3.0.txt
|
||||
|
||||
Assets from: workadventure@thecodingmachine.com
|
||||
|
||||
BASE assets:
|
||||
------------
|
||||
|
||||
- le-coq.png
|
||||
- logotcm.png
|
||||
- pin.png
|
||||
- tileset1-repositioning.png
|
||||
- tileset1.png
|
||||
- tileset2.2.png
|
||||
- tileset2.png
|
||||
- tileset3.2.png
|
||||
- tileset3.png
|
||||
- walls2.png
|
BIN
maps/Tuto/Male 13-4.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
maps/Tuto/fantasy.png
Normal file
After Width: | Height: | Size: 395 KiB |
74
maps/Tuto/scriptTuto.js
Normal file
@ -0,0 +1,74 @@
|
||||
var isFirstTimeTuto = false;
|
||||
var textFirstPopup = 'Hey ! This is how to start a discussion with someone ! You can be 4 max in a bubble.';
|
||||
var textSecondPopup = 'You can also use the chat to communicate ! ';
|
||||
var targetObjectTutoBubble ='Tutobubble';
|
||||
var targetObjectTutoChat ='tutoChat';
|
||||
var targetObjectTutoExplanation ='tutoExplanation';
|
||||
var popUpExplanation = undefined;
|
||||
function launchTuto (){
|
||||
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
|
||||
{
|
||||
label: "Next",
|
||||
className: "popUpElement",
|
||||
callback: (popup) => {
|
||||
popup.close();
|
||||
|
||||
WA.openPopup(targetObjectTutoChat, textSecondPopup, [
|
||||
{
|
||||
label: "Open Chat",
|
||||
className: "popUpElement",
|
||||
callback: (popup1) => {
|
||||
WA.sendChatMessage("Hey you can talk here too!", 'WA Guide');
|
||||
popup1.close();
|
||||
WA.openPopup("TutoFinal","You are good to go! You can meet the dev team and discover the features in the next room!",[
|
||||
{
|
||||
label: "Got it!",
|
||||
className : "success",callback:(popup2 => {
|
||||
popup2.close();
|
||||
WA.restorePlayerControl();
|
||||
})
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
])
|
||||
}
|
||||
}
|
||||
]);
|
||||
WA.disablePlayerControl();
|
||||
|
||||
}
|
||||
|
||||
|
||||
WA.onEnterZone('popupZone', () => {
|
||||
WA.displayBubble();
|
||||
if (!isFirstTimeTuto) {
|
||||
isFirstTimeTuto = true;
|
||||
launchTuto();
|
||||
}
|
||||
else {
|
||||
popUpExplanation = WA.openPopup(targetObjectTutoExplanation, 'Do you want to review the explanation?', [
|
||||
{
|
||||
label: "No",
|
||||
className: "error",
|
||||
callback: (popup) => {
|
||||
popup.close();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes",
|
||||
className: "success",
|
||||
callback: (popup) => {
|
||||
popup.close();
|
||||
launchTuto();
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
});
|
||||
|
||||
WA.onLeaveZone('popupZone', () => {
|
||||
if (popUpExplanation !== undefined) popUpExplanation.close();
|
||||
WA.removeBubble();
|
||||
})
|
BIN
maps/Tuto/shift.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
maps/Tuto/textTuto3.png
Normal file
After Width: | Height: | Size: 33 KiB |
4
maps/Tuto/tilesets/Atlas-refs-opengameart.txt
Normal file
@ -0,0 +1,4 @@
|
||||
LPC Atlas :
|
||||
https://opengameart.org/content/lpc-tile-atlas
|
||||
LPC Atlas 2 :
|
||||
https://opengameart.org/content/lpc-tile-atlas2
|
139
maps/Tuto/tilesets/LPC-Atlas-Attribution.txt
Normal file
@ -0,0 +1,139 @@
|
||||
License
|
||||
-------
|
||||
|
||||
CC-BY-SA 3.0:
|
||||
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||
- See the file: cc-by-sa-3.0.txt
|
||||
GNU GPL 3.0:
|
||||
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||
- See the file: gpl-3.0.txt
|
||||
|
||||
Note the file is based on the LCP contest readme so don't expect the exact little pieces used like the base one.
|
||||
*Additional license information.
|
||||
|
||||
Assets from:
|
||||
|
||||
LPC participants:
|
||||
----------------
|
||||
|
||||
Casper Nilsson
|
||||
*GNU GPL 3.0 or later
|
||||
email: casper.nilsson@gmail.com
|
||||
Freenode: CasperN
|
||||
OpenGameArt.org: C.Nilsson
|
||||
|
||||
- LPC C.Nilsson (2D art)
|
||||
|
||||
Daniel Eddeland
|
||||
*GNU GPL 3.0 or later
|
||||
- Tilesets of plants, props, food and environments, suitable for farming / fishing sims and other games.
|
||||
- Includes wheat, grass, sand tilesets, fence tilesets and plants such as corn and tomato.
|
||||
|
||||
|
||||
Johann CHARLOT
|
||||
*GNU LGPL Version 3.
|
||||
*Later versions are permitted.
|
||||
Homepage http://poufpoufproduction.fr
|
||||
Email johannc@poufpoufproduction.fr
|
||||
|
||||
- Shoot'em up graphic kit
|
||||
|
||||
Skyler Robert Colladay
|
||||
|
||||
- FeralFantom's Entry (2D art)
|
||||
|
||||
BASE assets:
|
||||
------------
|
||||
|
||||
Lanea Zimmerman (AKA Sharm)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- barrel.png
|
||||
- brackish.png
|
||||
- buckets.png
|
||||
- bridges.png
|
||||
- cabinets.png
|
||||
- cement.png
|
||||
- cementstair.png
|
||||
- chests.png
|
||||
- country.png
|
||||
- cup.png
|
||||
- dirt2.png
|
||||
- dirt.png
|
||||
- dungeon.png
|
||||
- grassalt.png
|
||||
- grass.png
|
||||
- holek.png
|
||||
- holemid.png
|
||||
- hole.png
|
||||
- house.png
|
||||
- inside.png
|
||||
- kitchen.png
|
||||
- lava.png
|
||||
- lavarock.png
|
||||
- mountains.png
|
||||
- rock.png
|
||||
- shadow.png
|
||||
- signs.png
|
||||
- stairs.png
|
||||
- treetop.png
|
||||
- trunk.png
|
||||
- waterfall.png
|
||||
- watergrass.png
|
||||
- water.png
|
||||
- princess.png and princess.xcf
|
||||
|
||||
|
||||
Stephen Challener (AKA Redshrike)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- female_walkcycle.png
|
||||
- female_hurt.png
|
||||
- female_slash.png
|
||||
- female_spellcast.png
|
||||
- male_walkcycle.png
|
||||
- male_hurt.png
|
||||
- male_slash.png
|
||||
- male_spellcast.png
|
||||
- male_pants.png
|
||||
- male_hurt_pants.png
|
||||
- male_fall_down_pants.png
|
||||
- male_slash_pants.png
|
||||
|
||||
|
||||
Charles Sanchez (AKA CharlesGabriel)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- bat.png
|
||||
- bee.png
|
||||
- big_worm.png
|
||||
- eyeball.png
|
||||
- ghost.png
|
||||
- man_eater_flower.png
|
||||
- pumpking.png
|
||||
- slime.png
|
||||
- small_worm.png
|
||||
- snake.png
|
||||
|
||||
|
||||
Manuel Riecke (AKA MrBeast)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- hairfemale.png and hairfemale.xcf
|
||||
- hairmale.png and hairmale.xcf
|
||||
- soldier.png
|
||||
- soldier_altcolor.png
|
||||
|
||||
|
||||
Daniel Armstrong (AKA HughSpectrum)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Castle work:
|
||||
|
||||
- castlewalls.png
|
||||
- castlefloors.png
|
||||
- castle_outside.png
|
||||
- castlefloors_outside.png
|
||||
- castle_lightsources.png
|
||||
|
||||
|
166
maps/Tuto/tilesets/LPC-Atlas2-Attribution2.txt
Normal file
@ -0,0 +1,166 @@
|
||||
License
|
||||
-------
|
||||
|
||||
CC-BY-SA 3.0:
|
||||
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||
- See the file: cc-by-sa-3.0.txt
|
||||
GNU GPL 3.0:
|
||||
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||
- See the file: gpl-3.0.txt
|
||||
|
||||
Note only some files from the entries are selected.
|
||||
*Additional license information.
|
||||
|
||||
Assets from:
|
||||
|
||||
LPC participants:
|
||||
----------------
|
||||
|
||||
Barbara Rivera
|
||||
|
||||
- tree,tombstone
|
||||
|
||||
Casper Nilsson
|
||||
*GNU GPL 3.0 or later
|
||||
email: casper.nilsson@gmail.com
|
||||
Freenode: CasperN
|
||||
OpenGameArt.org: C.Nilsson
|
||||
|
||||
- LPC C.Nilsson (2D art)
|
||||
|
||||
Chris Phillips
|
||||
|
||||
- tree
|
||||
|
||||
Daniel Eddeland
|
||||
*GNU GPL 3.0 or later
|
||||
|
||||
- Tilesets of plants, props, food and environments, suitable for farming / fishing sims and other games.
|
||||
- Includes wheat, grass, sand tilesets, fence tilesets and plants such as corn and tomato.
|
||||
- Also includes village/marketplace objects like sacks, food, some smithing equipment, tables and stalls.
|
||||
|
||||
|
||||
Anamaris and Krusmira (aka? Emilio J Sanchez)
|
||||
|
||||
- Sierra__Steampun-a-fy (with concept art)
|
||||
|
||||
Jonas Klinger
|
||||
|
||||
- Skorpio's SciFi Sprite Pack
|
||||
|
||||
Joshua Taylor
|
||||
|
||||
- Fruit and Veggie Inventory
|
||||
|
||||
Leo Villeveygoux
|
||||
|
||||
- Limestone Wall
|
||||
|
||||
Mark Weyer
|
||||
|
||||
- signpost+shadow
|
||||
|
||||
Matthew Nash
|
||||
|
||||
- Public Toilet Tileset
|
||||
|
||||
Skyler Robert Colladay
|
||||
|
||||
- FeralFantom's Entry
|
||||
|
||||
|
||||
BASE assets:
|
||||
------------
|
||||
|
||||
Lanea Zimmerman (AKA Sharm)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- barrel.png
|
||||
- brackish.png
|
||||
- buckets.png
|
||||
- bridges.png
|
||||
- cabinets.png
|
||||
- cement.png
|
||||
- cementstair.png
|
||||
- chests.png
|
||||
- country.png
|
||||
- cup.png
|
||||
- dirt2.png
|
||||
- dirt.png
|
||||
- dungeon.png
|
||||
- grassalt.png
|
||||
- grass.png
|
||||
- holek.png
|
||||
- holemid.png
|
||||
- hole.png
|
||||
- house.png
|
||||
- inside.png
|
||||
- kitchen.png
|
||||
- lava.png
|
||||
- lavarock.png
|
||||
- mountains.png
|
||||
- rock.png
|
||||
- shadow.png
|
||||
- signs.png
|
||||
- stairs.png
|
||||
- treetop.png
|
||||
- trunk.png
|
||||
- waterfall.png
|
||||
- watergrass.png
|
||||
- water.png
|
||||
- princess.png and princess.xcf
|
||||
|
||||
|
||||
Stephen Challener (AKA Redshrike)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- female_walkcycle.png
|
||||
- female_hurt.png
|
||||
- female_slash.png
|
||||
- female_spellcast.png
|
||||
- male_walkcycle.png
|
||||
- male_hurt.png
|
||||
- male_slash.png
|
||||
- male_spellcast.png
|
||||
- male_pants.png
|
||||
- male_hurt_pants.png
|
||||
- male_fall_down_pants.png
|
||||
- male_slash_pants.png
|
||||
|
||||
|
||||
Charles Sanchez (AKA CharlesGabriel)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- bat.png
|
||||
- bee.png
|
||||
- big_worm.png
|
||||
- eyeball.png
|
||||
- ghost.png
|
||||
- man_eater_flower.png
|
||||
- pumpking.png
|
||||
- slime.png
|
||||
- small_worm.png
|
||||
- snake.png
|
||||
|
||||
|
||||
Manuel Riecke (AKA MrBeast)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- hairfemale.png and hairfemale.xcf
|
||||
- hairmale.png and hairmale.xcf
|
||||
- soldier.png
|
||||
- soldier_altcolor.png
|
||||
|
||||
|
||||
Daniel Armstrong (AKA HughSpectrum)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Castle work:
|
||||
|
||||
- castlewalls.png
|
||||
- castlefloors.png
|
||||
- castle_outside.png
|
||||
- castlefloors_outside.png
|
||||
- castle_lightsources.png
|
||||
|
||||
|
2
maps/Tuto/tilesets/LPC-indoors.txt
Normal file
@ -0,0 +1,2 @@
|
||||
https://opengameart.org/content/lpc-interior-castle-tiles
|
||||
credit Lanea Zimmerman
|
55
maps/Tuto/tilesets/LPC-interiors-house-credits.txt
Normal file
@ -0,0 +1,55 @@
|
||||
= Wooden floor(CC-BY-SA) =
|
||||
* Horizontal wooden floor by Lanea Zimmerman (AKA Sharm)
|
||||
* Horizontal wooden floor with hole by Lanea Zimmerman (AKA Sharm) and Tuomo Untinen
|
||||
* Vertical wooden floor by Tuomo Untinen
|
||||
* Vertical wooden floor with hole by Tuomo Untinen
|
||||
= Wooden wall topping (CC-BY-SA) =
|
||||
* Tuomo Untinen
|
||||
= Pile of barrels =
|
||||
* Based LPC base tiles by Lanea Zimmerman (AKA Sharm)
|
||||
= Decorational stuff (CC-BY-SA) =
|
||||
* Green bottle
|
||||
* Wine glass and bottle
|
||||
* Hanging sacks
|
||||
* Wall mounted rope and ropes
|
||||
* Wall mounted swords
|
||||
* Wall mounted kite shield
|
||||
* Wall hole
|
||||
By Tuomo Untinen
|
||||
* Small sack from LPC farming tileset by Daniel Eddeland (http://opengameart.org/content/lpc- farming-tilesets-magic-animations-and-ui-elements)
|
||||
* Purple bottles and gray lantern from Hyptosis Mage city
|
||||
* Green and blue bottle by Tuomo Untinen
|
||||
= Wall clock (CC-BY-SA) =
|
||||
* Lanea Zimmerman AKA Sharm
|
||||
* Tuomo Untinen (Scaled down and animation)
|
||||
= Stone floor (CC-BY-SA)=
|
||||
* Tuomo Untinen
|
||||
= Cobble stone floor (CC-BY-SA)=
|
||||
* Based on LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||
= Cabinets and kitchen stuff including metal stove(CC-BY-SA) =
|
||||
* Based on LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||
* Cutboard is made by Hyptosis
|
||||
* Sacks based on LPC farming tileset by Daniel Eddeland (http://opengameart.org/content/lpc- farming-tilesets-magic-animations-and-ui-elements)
|
||||
* Spears by Tuomo Untinen
|
||||
* Vertical chest by Tuomo Untinen based on LPC base tiles Lanea Zimmerman (AKA Sharm)
|
||||
Manuel Riecke (AKA MrBeast)
|
||||
= Skull (CC-BY-SA) =
|
||||
* http://opengameart.org/content/lpc-dungeon-elements
|
||||
* Graphical artist Lanea Zimmerman AKA Sharm
|
||||
* Contributor William Thompson
|
||||
= pile sacks =
|
||||
* LPC farming tileset by Daniel Eddeland (http://opengameart.org/content/lpc- farming-tilesets-magic-animations-and-ui-elements)
|
||||
= Pile of papers(CC-BY-SA) =
|
||||
* Based on caeles papers
|
||||
= Armor shelves(CC-BY-SA) =
|
||||
* Based on LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||
* Armors by: Adapted by Matthew Krohn from art created by Johannes Sjölund
|
||||
= Table lamp =
|
||||
* Tuomo Untinen
|
||||
= Distiller =
|
||||
* Table is from LPC base tileset by Lanea Zimmerman (AKA Sharm)
|
||||
* Distiller by Tuomo Untinen
|
||||
= Fireplace =
|
||||
* Tuomo Untinen
|
||||
* Inspired by Lanea Zimmerman (AKA Sharm) Fireplace
|
||||
|
28
maps/Tuto/tilesets/LPC-leaf-credits.txt
Normal file
@ -0,0 +1,28 @@
|
||||
License
|
||||
-------
|
||||
|
||||
CC-BY-SA 3.0:
|
||||
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||
GNU GPL 3.0:
|
||||
- http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
If you need to figure out exactly who made what please see the Liberated Pixel Cup entries.
|
||||
|
||||
Liberated Pixel Cup Assets:
|
||||
http://opengameart.org/lpc-art-entries
|
||||
|
||||
LPC participants:
|
||||
----------------
|
||||
|
||||
Johann CHARLOT
|
||||
Homepage http://poufpoufproduction.fr
|
||||
Email johannc@poufpoufproduction.fr
|
||||
- Shoot'em up graphic kit
|
||||
|
||||
|
||||
|
||||
Recolored Leaves
|
||||
|
||||
William Thompson
|
||||
Email: william.thompsonj@gmail.com
|
||||
OpenGameArt.org: williamthompsonj
|
1
maps/Tuto/tilesets/LPC-leafs-refs.txt
Normal file
@ -0,0 +1 @@
|
||||
https://opengameart.org/content/lpc-leaf-recolor
|
1
maps/Tuto/tilesets/LPC-submissions-refs.txt
Normal file
@ -0,0 +1 @@
|
||||
https://opengameart.org/content/lpc-submissions-merged
|
1
maps/Tuto/tilesets/LPc-mountains-refs.txt
Normal file
@ -0,0 +1 @@
|
||||
https://opengameart.org/content/lpc-mountains
|
344
maps/Tuto/tilesets/LPc-submissions-Final Attribution.txt
Normal file
@ -0,0 +1,344 @@
|
||||
License
|
||||
-------
|
||||
|
||||
CC-BY-SA 3.0:
|
||||
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
GNU GPL 3.0:
|
||||
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Terrain and Outside:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Lanea Zimmerman (AKA Sharm) - CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- barrels
|
||||
- darkishgreen water
|
||||
- buckets
|
||||
- bridges
|
||||
- cement
|
||||
- cement stairs
|
||||
- chests
|
||||
- cup
|
||||
- light dirt
|
||||
- mid dirt
|
||||
- dungeon
|
||||
- grass1 (leftmost)
|
||||
- grass2 (Middle grass)
|
||||
- hole1 (left hole near lava)
|
||||
- hole2 (middle hole)
|
||||
- hole3 (black whole next to transparent water)
|
||||
- lava
|
||||
- lavarock (black dirt)
|
||||
- mountains ridge (right of the water tiles)
|
||||
- white rocks
|
||||
- waterfall
|
||||
- water/grass
|
||||
- water (transparent water beside black hole)
|
||||
|
||||
Daniel Eddeland CC-BY-SA-3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-farming-tilesets-magic-animations-and-ui-elements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Plowed Ground
|
||||
- Water reeds
|
||||
- Sand
|
||||
- Sand with water
|
||||
- Tall grass
|
||||
- Wheat
|
||||
- Young wheet (green wheat left of tall grass)
|
||||
|
||||
William Thompsonj
|
||||
https://opengameart.org/content/lpc-sandrock-alt-colors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- sand (near the wheat)
|
||||
- sand with water
|
||||
- grey dirt left of the lava)
|
||||
|
||||
|
||||
Matthew Krohn (Makrohn)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Cave / Mountain steps originals from Lanea Zimmerman
|
||||
|
||||
Matthew Nash
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- stone tile (purplish color beside the dirt path bottom right corner)
|
||||
|
||||
Nushio
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Ice tiles
|
||||
- Snow
|
||||
- snow/ice
|
||||
- Snow water
|
||||
|
||||
|
||||
Casper Nilson
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- grass with flowers
|
||||
- stone pattern below cement
|
||||
- tree stumps
|
||||
- lily pads
|
||||
|
||||
|
||||
|
||||
|
||||
MISSING:
|
||||
Bricks / Paths above lillypads and left of barrels
|
||||
The recoloing of the rocks on the left
|
||||
The bigger stump
|
||||
Bottom right tiles
|
||||
Outside stone head and columns
|
||||
Green water
|
||||
Ladders
|
||||
Brown path
|
||||
Sewer
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Outside Objects:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Lanea Zimmerman (AKA Sharm) CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Tree trunks (for evergreen and oak)
|
||||
- Tree Tops (for evergreen and oak)
|
||||
|
||||
Daniel Eddeland
|
||||
(https://opengameart.org/content/lpc-farming-tilesets-magic-animations-and-ui-elements)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Farming stuff including food / crops / sack of grains
|
||||
- Logs / Anvils
|
||||
- Fish / boats / pier
|
||||
- Bazaar / Merchant displays
|
||||
- Wooden Fences
|
||||
|
||||
Caeless
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Shadowless veggies (originally made by Daniel Eddeland)
|
||||
|
||||
William Thompsonj
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- fallen leaves
|
||||
|
||||
Casper Nilsson
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Metal Fence
|
||||
- Wheelbarrows
|
||||
- tent
|
||||
- Gravestones
|
||||
- harbor (stone platform in water)
|
||||
- long boat
|
||||
|
||||
Barbara Rivera / C Phillips
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Leafless tree
|
||||
|
||||
Skorpio
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Trash / Barrel ( Top right corner)
|
||||
|
||||
MISSING:
|
||||
Bricks bottom left
|
||||
- Mushrooms need attributions
|
||||
Bricks/tiles above pier
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Exterior:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Lanea Zimmerman (AKA Sharm) CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- house (light red brick wall on far left and purple roof below it)
|
||||
- house white door frame / brown door and white windows.
|
||||
- signs under gold and drak brown brick house wall
|
||||
|
||||
Daniel Armstrong (AKA HughSpectrum)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- castle walls
|
||||
- castle floors
|
||||
- castle outside
|
||||
- castle floors outside
|
||||
|
||||
Xenodora CC-BY-3.0 / GPL 3.0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- water well
|
||||
|
||||
Tuomo Untinen CC-BY-3.0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- stone bridge
|
||||
- silver windows
|
||||
|
||||
Casper Nilsson
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Asain themed shrine including red lantern
|
||||
- foodog statue
|
||||
- Toro
|
||||
- Cherry blossom tree
|
||||
- Torii
|
||||
- Grey Roof
|
||||
- Flag poles
|
||||
- Fancy white and black entrance
|
||||
- white door
|
||||
- green and white walls / roof / windows
|
||||
- shrub plant in white square pot
|
||||
- flower box
|
||||
Steampun-a-fy: Amaris / Krusimira
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- dark purple brick near purple roof
|
||||
- bronze and wood house (gold and dark brick)
|
||||
- dark wooden stairs
|
||||
- gold and dark chimney
|
||||
- grey door
|
||||
- gears
|
||||
- pipes
|
||||
- dark wooden windows
|
||||
|
||||
Leo Villeveygoux
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- white bricks (limestone wall)
|
||||
|
||||
Skyler Robert Collady
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Grey home assets
|
||||
|
||||
MISSING attributions:
|
||||
Graves bottom right
|
||||
Water filled boat
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Interior
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Lanea Zimmerman (AKA Sharm) CC-BY-3.0 / GPL 3.0 / GPL 2.0 / OGA-BY-3.0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- bookshelves (to the left of light brown chairs)
|
||||
- cabinents above bookshelves
|
||||
- counters (to the right of the kitchen table (between the white bathroom tiles) and bottom left by the rug.
|
||||
- blue wallpaper
|
||||
- kitchen furnace, pots and sink.
|
||||
- country inside (blue bed and light brown chairs)
|
||||
- red royal bed with white pillows.
|
||||
- Mahogany kitchen table (near the dungeon bed with a black blanket)
|
||||
- Yellow curtains
|
||||
- Flower pot with tall pink flower
|
||||
- Empty flower pots
|
||||
- Chairs with gold seats (between the fireplaces)
|
||||
- Fireplaces
|
||||
- White and red mahogany stairs
|
||||
- Double Rounded doors
|
||||
- Flower vases
|
||||
- Purple/Blue Tiles near white brick wall
|
||||
- White brick wall
|
||||
- White Columns
|
||||
- Black candle-holder stand with candles
|
||||
- Royal rug beside cabinents
|
||||
- White stairs with runway / platform
|
||||
- Grandfather clock
|
||||
- Blue wallpaper with woodem trim
|
||||
- Wood tiles
|
||||
- Long painting
|
||||
- Royal chairs ( gold seats)
|
||||
- Rounded white windows
|
||||
- Portrait painting
|
||||
- small end / round tables
|
||||
- Royal bed with red pillows
|
||||
- white china
|
||||
|
||||
By Sharm but Commissioned by William Thompsonj
|
||||
- campfire
|
||||
- skeletons
|
||||
- dungeon beds
|
||||
- wood chairs and tables between the calderon and the fancy door rounded door
|
||||
- calderon
|
||||
- cobwebs
|
||||
- dungeon prison wall and door/gate (beside fancy red and gold rugs)
|
||||
- dirt by the dungeon beds
|
||||
- rat
|
||||
|
||||
|
||||
Daniel Armstrong (Aka HughSpectrum)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- castle light Sources (torches)
|
||||
- red carpets
|
||||
- grey brick top of walls
|
||||
|
||||
Matthew Nash
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Public Toilets
|
||||
- Bathroom tiles (white and black)
|
||||
|
||||
Tuomo Untinen
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Pots with cover (based from Sharm's pots)
|
||||
- Yellow stone floor
|
||||
- Short paintings
|
||||
- royal chair modification
|
||||
- cupboards based on Sharm's (with china)
|
||||
- small footchair
|
||||
- piano
|
||||
|
||||
|
||||
MISSING attributions:
|
||||
Some bottomleft tiles
|
||||
Banners
|
||||
Sideways table
|
||||
Stacked barrels
|
||||
Stacked chess
|
||||
Things to left of the pots
|
||||
Some of the furniture below the beds
|
||||
Some of the single beds
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*Interior 2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lanea Zimmerman (AKA Sharm)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- brown stairs with blue
|
||||
- Fountain
|
||||
- Pool
|
||||
- FLoor tiles
|
||||
- Everything between top row (between toilet stalls and bookcases) down to the floor tiles
|
||||
|
||||
|
||||
Xenodora
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Steel floor
|
||||
|
||||
|
||||
Tuomo Untinen
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Armor and sheilds
|
||||
- Cheese and bread
|
||||
- Ship
|
||||
|
||||
|
||||
Janna - CC0
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Beds
|
||||
- Dressers / book shelves
|
||||
- Wardrobe
|
||||
|
||||
|
||||
Casper Nilsson
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Red and Blue stairs
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Extensions Folder:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lanea Zimmerman (AKA Sharm)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
https://opengameart.org/content/lpc-adobe-building-set
|
||||
https://opengameart.org/content/lpc-arabic-elements
|
||||
- Adobe / Arabic - Commissioned by William Thompsonj
|
12
maps/Tuto/tilesets/Specials-attribution.txt
Normal file
@ -0,0 +1,12 @@
|
||||
License
|
||||
-------
|
||||
|
||||
CC-BY-SA 3.0:
|
||||
- http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
Author :
|
||||
|
||||
Jacques-Olivier Farcy
|
||||
https://interstices.ouvaton.org
|
||||
https://twitter.com/JO_Interstices
|
||||
|
106
maps/Tuto/tilesets/outdoor/CREDITS-plants.txt
Normal file
@ -0,0 +1,106 @@
|
||||
## Flowers / Plants / Fungi / Wood
|
||||
|
||||
"[LPC] Flowers / Plants / Fungi / Wood," by bluecarrot16, Guido Bos, Ivan Voirol (Silver IV), SpiderDave, William.Thompsonj, Yar, Stephen Challener and the Open Surge team (http://opensnc.sourceforge.net), Gaurav Munjal, Johann Charlot, Casper Nilsson, Jetrel, Zabin, Hyptosis, Surt, Lanea Zimmerman, George Bailey, ansimuz, Buch, and the Open Pixel Project contributors (OpenPixelProject.com).
|
||||
CC-BY-SA 3.0.
|
||||
|
||||
Based on:
|
||||
|
||||
[LPC] Guido Bos entries cut up
|
||||
Guido Bos
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-guido-bos-entries-cut-up
|
||||
|
||||
Basic map 32x32 by Silver IV
|
||||
Ivan Voirol (Silver IV)
|
||||
CC-BY 3.0 / GPL 3.0 / GPL 2.0
|
||||
https://opengameart.org/content/basic-map-32x32-by-silver-iv
|
||||
|
||||
Flowers
|
||||
SpiderDave
|
||||
CC0
|
||||
https://opengameart.org/content/flowers
|
||||
|
||||
[LPC] Leaf Recolor
|
||||
William.Thompsonj
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-leaf-recolor
|
||||
|
||||
Isometric 64x64 Outside Tileset
|
||||
Yar
|
||||
CC-BY 3.0
|
||||
https://opengameart.org/content/isometric-64x64-outside-tileset
|
||||
|
||||
32x32 (and 16x16) RPG Tiles--Forest and some Interior Tiles
|
||||
Stephen Challener and the Open Surge team (http://opensnc.sourceforge.net)commissioned by Gaurav Munjal
|
||||
CC-BY 3.0
|
||||
https://opengameart.org/content/32x32-and-16x16-rpg-tiles-forest-and-some-interior-tiles
|
||||
|
||||
Lots of Hyptosis' tiles organized!
|
||||
Hyptosis
|
||||
CC-BY 3.0
|
||||
https://opengameart.org/content/lots-of-hyptosis-tiles-organized
|
||||
|
||||
Generic Platformer Tiles
|
||||
surt
|
||||
CC0
|
||||
http://opengameart.org/content/generic-platformer-tiles
|
||||
|
||||
old frogatto tile art
|
||||
Guido Bos
|
||||
CC0
|
||||
https://opengameart.org/content/old-frogatto-tile-art
|
||||
|
||||
LPC: Interior Castle Tiles
|
||||
Lanea Zimmerman
|
||||
CC-BY-3.0 / GPL 3.0
|
||||
http://opengameart.org/content/lpc-interior-castle-tiles
|
||||
|
||||
RPG item set
|
||||
Jetrel
|
||||
CC0
|
||||
https://opengameart.org/content/rpg-item-set
|
||||
|
||||
Shoot'em up graphic kit
|
||||
Johann Charlot
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/shootem-up-graphic-kit
|
||||
|
||||
LPC C.Nilsson
|
||||
Casper Nilsson
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-cnilsson
|
||||
|
||||
Lots of trees and plants from OGA (DB32) tilesets pack 1
|
||||
Jetrel, Zabin, Hyptosis, Surt
|
||||
CC0
|
||||
https://opengameart.org/content/lots-of-trees-and-plants-from-oga-db32-tilesets-pack-1
|
||||
|
||||
Trees & Bushes
|
||||
ansimuz
|
||||
CC0
|
||||
https://opengameart.org/content/trees-bushes
|
||||
|
||||
Outdoor tiles, again
|
||||
Buch <https://opengameart.org/users/buch>
|
||||
CC-BY 2.0
|
||||
https://opengameart.org/content/outdoor-tiles-again
|
||||
|
||||
16x16 Game Assets
|
||||
George Bailey
|
||||
CC-BY 4.0
|
||||
https://opengameart.org/content/16x16-game-assets
|
||||
|
||||
Tuxemon tileset
|
||||
Buch
|
||||
CC-BY-SA 3.0
|
||||
https://opengameart.org/content/tuxemon-tileset
|
||||
|
||||
Orthographic outdoor tiles
|
||||
Buch
|
||||
CC0
|
||||
https://opengameart.org/content/orthographic-outdoor-tiles
|
||||
|
||||
OPP2017 - Jungle and temple set
|
||||
OpenPixelProject.com
|
||||
CC0
|
||||
https://opengameart.org/content/opp2017-jungle-and-temple-set
|
After Width: | Height: | Size: 295 KiB |
BIN
maps/Tuto/tilesets/outdoor/LPC-submissions-outside.png
Normal file
After Width: | Height: | Size: 192 KiB |
BIN
maps/Tuto/tilesets/outdoor/LPC-terrains-subimissions-outside.png
Normal file
After Width: | Height: | Size: 327 KiB |
101
maps/Tuto/tilesets/outdoor/decorations-medieval-credits.txt
Normal file
@ -0,0 +1,101 @@
|
||||
## Medieval
|
||||
|
||||
[LPC] Hanging signs
|
||||
Reemax
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-hanging-signs
|
||||
|
||||
Liberated Pixel Cup (LPC) Base Assets
|
||||
Lanea Zimmerman (Sharm)
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/liberated-pixel-cup-lpc-base-assets-sprites-map-tiles
|
||||
|
||||
[LPC] City outside
|
||||
Reemax (Tuomo Untinen), Xenodora, Sharm, Johann C, Johannes Sjölund
|
||||
CC-BY-SA 3.0 / GPL 3.0 / GPL 2.0
|
||||
https://opengameart.org/content/lpc-city-outside
|
||||
|
||||
[LPC] Cavern and ruin tiles
|
||||
CC-BY-SA 3.0 / GPL 3.0 / GPL 2.0
|
||||
Reemax, Sharm, Hyptosis, Johann C, HughSpectrum, Redshrike, William.Thompsonj, wulax,
|
||||
https://opengameart.org/node/33913
|
||||
|
||||
Statues & Fountains Collection
|
||||
Casper Nilsson, Daniel Cook, Rayane Félix (RayaneFLX), Wolthera van Hövell tot Westerflier (TheraHedwig), Hyptosis, mold, Zachariah Husiar (Zabin), & Clint Bellanger
|
||||
CC-BY-SA 3.0
|
||||
https://opengameart.org/content/statues-fountains-collection
|
||||
|
||||
LPC C.Nilsson
|
||||
Casper Nilsson
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-cnilsson
|
||||
|
||||
LPC Style Well
|
||||
CC-BY 3.0 / GPL 3.0
|
||||
Xenodora, Sharm
|
||||
https://opengameart.org/content/lpc-style-well
|
||||
|
||||
RPG item set
|
||||
Jetrel
|
||||
CC0
|
||||
https://opengameart.org/content/rpg-item-set
|
||||
|
||||
[LPC] Guido Bos entries cut up
|
||||
Guido Bos
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-guido-bos-entries-cut-up
|
||||
|
||||
LPC Sign Post
|
||||
Nemisys
|
||||
CC-BY 3.0 / CC-BY-SA 3.0 / GPL 3.0 / OGA-BY 3.0
|
||||
https://opengameart.org/content/lpc-sign-post
|
||||
|
||||
[LPC] Signposts, graves, line cloths and scare crow
|
||||
Reemax
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-signposts-graves-line-cloths-and-scare-crow
|
||||
|
||||
[LPC] Hanging signs
|
||||
Reemax
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
https://opengameart.org/content/lpc-hanging-signs
|
||||
|
||||
Hyptosis
|
||||
Mage City Arcanos
|
||||
CC0
|
||||
https://opengameart.org/content/mage-city-arcanos
|
||||
|
||||
[LPC] Street Lamp
|
||||
Curt
|
||||
CC-BY 3.0
|
||||
https://opengameart.org/content/lpc-street-lamp
|
||||
|
||||
[LPC] Misc
|
||||
Lanea Zimmerman (Sharm), William.Thompsonj
|
||||
CC-BY 3.0 / GPL 3.0 / GPL 2.0 / OGA-BY 3.0
|
||||
https://opengameart.org/content/lpc-misc
|
||||
|
||||
RPG Tiles: Cobble stone paths & town objects
|
||||
https://opengameart.org/content/rpg-tiles-cobble-stone-paths-town-objects
|
||||
Zabin, Daneeklu, Jetrel, Hyptosis, Redshrike, Bertram.
|
||||
CC-BY-SA 3.0
|
||||
|
||||
[LPC] Farming tilesets, magic animations and UI elements
|
||||
https://opengameart.org/content/lpc-farming-tilesets-magic-animations-and-ui-elements
|
||||
Daniel Eddeland (daneeklu)
|
||||
CC-BY-SA 3.0 / GPL 3.0
|
||||
|
||||
RPG item set
|
||||
Jetrel
|
||||
CC0
|
||||
https://opengameart.org/content/rpg-item-set
|
||||
|
||||
RPG Indoor Tileset: Expansion 1
|
||||
Redshrike
|
||||
CC-BY 3.0 / GPL 3.0 / GPL 2.0 / OGA-BY 3.0
|
||||
https://opengameart.org/content/rpg-indoor-tileset-expansion-1
|
||||
|
||||
[LPC] Dungeon Elements
|
||||
Lanea Zimmerman (Sharm), William.Thompsonj
|
||||
CC-BY 3.0 / GPL 3.0 / GPL 2.0 / OGA-BY 3.0
|
||||
https://opengameart.org/content/lpc-dungeon-elements
|
BIN
maps/Tuto/tilesets/outdoor/decorations-medieval.png
Normal file
After Width: | Height: | Size: 165 KiB |
BIN
maps/Tuto/tilesets/outdoor/plants.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
maps/Tuto/tilesets/specials/ALL.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
706
maps/Tuto/tutoV3.json
Normal file
99
maps/tests/goToPage.json
Normal file
@ -0,0 +1,99 @@
|
||||
{ "compressionlevel":-1,
|
||||
"height":20,
|
||||
"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, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":20,
|
||||
"id":2,
|
||||
"name":"start",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":20,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":20,
|
||||
"id":4,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":20,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"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, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":20,
|
||||
"id":3,
|
||||
"name":"popupZone",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"zone",
|
||||
"type":"string",
|
||||
"value":"popUpGoToPageZone"
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":20,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":5,
|
||||
"name":"floorLayer",
|
||||
"objects":[
|
||||
{
|
||||
"height":59,
|
||||
"id":1,
|
||||
"name":"popUp",
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":152,
|
||||
"x":247,
|
||||
"y":11
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":6,
|
||||
"nextobjectid":2,
|
||||
"orientation":"orthogonal",
|
||||
"properties":[
|
||||
{
|
||||
"name":"script",
|
||||
"type":"string",
|
||||
"value":"goToPageScript.js"
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.5.0",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.5,
|
||||
"width":20
|
||||
}
|
49
maps/tests/goToPageScript.js
Normal file
@ -0,0 +1,49 @@
|
||||
var zoneName = "popUpGoToPageZone";
|
||||
var urlPricing = "https://workadventu.re/pricing";
|
||||
var urlGettingStarted = "https://workadventu.re/getting-started";
|
||||
var isCoWebSiteOpened = false;
|
||||
|
||||
WA.onChatMessage((message => {
|
||||
WA.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot');
|
||||
}));
|
||||
|
||||
WA.onEnterZone(zoneName, () => {
|
||||
WA.openPopup("popUp","Open Links",[
|
||||
{
|
||||
label: "Open Tab",
|
||||
className: "popUpElement",
|
||||
callback: (popup => {
|
||||
WA.openTab(urlPricing);
|
||||
popup.close();
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Go To Page", className : "popUpElement",
|
||||
callback:(popup => {
|
||||
WA.goToPage(urlPricing);
|
||||
popup.close();
|
||||
})
|
||||
|
||||
}
|
||||
,
|
||||
{
|
||||
label: "openCoWebSite", className : "popUpElement",
|
||||
callback:(popup => {
|
||||
WA.openCoWebSite(urlPricing);
|
||||
isCoWebSiteOpened = true;
|
||||
popup.close();
|
||||
})
|
||||
|
||||
}]);
|
||||
})
|
||||
|
||||
WA.onLeaveZone(zoneName, () => {
|
||||
if (isCoWebSiteOpened) {
|
||||
WA.closeCoWebSite();
|
||||
isCoWebSiteOpened = false;
|
||||
}
|
||||
})
|
||||
|
||||
WA.onLeaveZone('popupZone', () => {
|
||||
|
||||
})
|
25
maps/tests/iframe.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="http://play.workadventure.localhost/iframe_api.js"></script>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="sendchat">Send chat message</button>
|
||||
<script>
|
||||
document.getElementById('sendchat').onclick = () => {
|
||||
WA.sendChatMessage('Hello world!', 'Mr Robot');
|
||||
}
|
||||
</script>
|
||||
<div id="chatSent"></div>
|
||||
<script>
|
||||
WA.onChatMessage((message => {
|
||||
const chatDiv = document.createElement('p');
|
||||
chatDiv.innerText = message;
|
||||
document.getElementById('chatSent').append(chatDiv);
|
||||
}));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
94
maps/tests/iframe_api.json
Normal file
@ -0,0 +1,94 @@
|
||||
{ "compressionlevel":-1,
|
||||
"editorsettings":
|
||||
{
|
||||
"export":
|
||||
{
|
||||
"target":"."
|
||||
}
|
||||
},
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 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":5,
|
||||
"name":"iframe_api",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"openWebsite",
|
||||
"type":"string",
|
||||
"value":"iframe.html"
|
||||
},
|
||||
{
|
||||
"name":"openWebsiteAllowApi",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":6,
|
||||
"nextobjectid":1,
|
||||
"orientation":"orthogonal",
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.3.3",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.2,
|
||||
"width":10
|
||||
}
|
79
maps/tests/script.js
Normal file
@ -0,0 +1,79 @@
|
||||
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 ='tutoBobble';
|
||||
var targetObjectTutoChat ='tutoChat';
|
||||
var popUpExplanation = undefined;
|
||||
function launchTuto (){
|
||||
WA.openPopup(targetObjectTutoBubble, textFirstPopup, [
|
||||
{
|
||||
label: "Next",
|
||||
className: "popUpElement",
|
||||
callback: (popup) => {
|
||||
popup.close();
|
||||
|
||||
WA.openPopup(targetObjectTutoChat, textSecondPopup, [
|
||||
{
|
||||
label: "Open Chat",
|
||||
className: "popUpElement",
|
||||
callback: (popup1) => {
|
||||
WA.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
|
||||
popup1.close();
|
||||
WA.restorePlayerControl();
|
||||
}
|
||||
}
|
||||
|
||||
])
|
||||
}
|
||||
}
|
||||
]);
|
||||
WA.disablePlayerControl();
|
||||
|
||||
}
|
||||
WA.onChatMessage((message => {
|
||||
console.log('CHAT MESSAGE RECEIVED BY SCRIPT');
|
||||
WA.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot');
|
||||
}));
|
||||
|
||||
WA.onEnterZone('myTrigger', () => {
|
||||
WA.sendChatMessage("Don't step on my carpet!", 'Poly Parrot');
|
||||
})
|
||||
|
||||
WA.onLeaveZone('popupZone', () => {
|
||||
})
|
||||
|
||||
WA.onEnterZone('notExist', () => {
|
||||
WA.sendChatMessage("YOU SHOULD NEVER SEE THIS", 'Poly Parrot');
|
||||
})
|
||||
|
||||
WA.onEnterZone('popupZone', () => {
|
||||
WA.displayBubble();
|
||||
if (!isFirstTimeTuto) {
|
||||
isFirstTimeTuto = true;
|
||||
launchTuto();
|
||||
}
|
||||
else popUpExplanation = WA.openPopup(targetObjectTutoChat,'Do you want to review the explanation ? ', [
|
||||
{
|
||||
label: "No",
|
||||
className: "popUpElementReviewexplanation",
|
||||
callback: (popup) => {
|
||||
popup.close();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes",
|
||||
className: "popUpElementReviewexplanation",
|
||||
callback: (popup) => {
|
||||
popup.close();
|
||||
launchTuto();
|
||||
}
|
||||
}
|
||||
])
|
||||
});
|
||||
|
||||
WA.onLeaveZone('popupZone', () => {
|
||||
if (popUpExplanation !== undefined) popUpExplanation.close();
|
||||
WA.removeBubble();
|
||||
})
|
135
maps/tests/script_api.json
Normal file
@ -0,0 +1,135 @@
|
||||
{ "compressionlevel":-1,
|
||||
"editorsettings":
|
||||
{
|
||||
"export":
|
||||
{
|
||||
"target":"."
|
||||
}
|
||||
},
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":6,
|
||||
"name":"triggerZone",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"zone",
|
||||
"type":"string",
|
||||
"value":"myTrigger"
|
||||
}],
|
||||
"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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":7,
|
||||
"name":"popupZone",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"zone",
|
||||
"type":"string",
|
||||
"value":"popupZone"
|
||||
}],
|
||||
"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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[
|
||||
{
|
||||
"height":147.135497146101,
|
||||
"id":1,
|
||||
"name":"myPopup2",
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":104.442827410047,
|
||||
"x":142.817125079855,
|
||||
"y":147.448134926559
|
||||
},
|
||||
{
|
||||
"height":132.434722966794,
|
||||
"id":2,
|
||||
"name":"myPopup1",
|
||||
"rotation":0,
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":125.735549178518,
|
||||
"x":13.649632619596,
|
||||
"y":50.8502491249093
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":8,
|
||||
"nextobjectid":3,
|
||||
"orientation":"orthogonal",
|
||||
"properties":[
|
||||
{
|
||||
"name":"script",
|
||||
"type":"string",
|
||||
"value":"script.js"
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"1.4.3",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.4,
|
||||
"width":10
|
||||
}
|