diff --git a/contrib/docker/.env.prod.template b/contrib/docker/.env.prod.template index c0c10181..5e9adc87 100644 --- a/contrib/docker/.env.prod.template +++ b/contrib/docker/.env.prod.template @@ -1,20 +1,118 @@ +# Security +# + +SECRET_KEY= +ADMIN_API_TOKEN= + +# +# Networking +# + # The base domain DOMAIN=workadventure.localhost -DEBUG_MODE=false +# Subdomains +# MUST match the DOMAIN variable above +FRONT_HOST=front.workadventure.localhost +PUSHER_HOST=pusher.workadventure.localhost +BACK_HOST=api.workadventure.localhost +MAPS_HOST=maps.workadventure.localhost +ICON_HOST=icon.workadventure.localhost + +# SAAS admin panel +ADMIN_API_URL= + +# +# Basic configuration +# + +# The directory to store data in +DATA_DIR=./wa + +# The URL used by default, in the form: "/_/global/map/url.json" +START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json + +# If you want to have a contact page in your menu, +# you MUST set CONTACT_URL to the URL of the page that you want +CONTACT_URL= + +MAX_PER_GROUP=4 +MAX_USERNAME_LENGTH=8 +DISABLE_ANONYMOUS=false + +# The version of the docker image to use +# MUST uncomment "image" keys in the docker-compose file for it to be effective +VERSION=master + +TZ=Europe/Paris + +# +# Jitsi +# + JITSI_URL=meet.jit.si -# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret +# If your Jitsi environment has authentication set up, +# you MUST set JITSI_PRIVATE_MODE to "true" +# and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret JITSI_PRIVATE_MODE=false JITSI_ISS= SECRET_JITSI_KEY= +# +# Turn/Stun +# + # URL of the TURN server (needed to "punch a hole" through some networks for P2P connections) TURN_SERVER= TURN_USER= TURN_PASSWORD= +# If your Turn server is configured to use the Turn REST API, you MUST put the shared auth secret here. +# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file. +# Keep empty if you are sharing hard coded / clear text credentials. +TURN_STATIC_AUTH_SECRET= +# URL of the STUN server +STUN_SERVER= -# The URL used by default, in the form: "/_/global/map/url.json" -START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json +# +# Certificate config +# # The email address used by Let's encrypt to send renewal warnings (compulsory) ACME_EMAIL= + +# +# Additional app configs +# Configuration for apps which are not workadventure itself +# + +# openID +OPID_CLIENT_ID= +OPID_CLIENT_SECRET= +OPID_CLIENT_ISSUER= +OPID_CLIENT_REDIRECT_URL= +OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen +OPID_PROFILE_SCREEN_PROVIDER= + + +# +# Advanced configuration +# Generally does not need to be changed +# + +# Networking +HTTP_PORT=80 +HTTPS_PORT=443 + +# Workadventure settings +DISABLE_NOTIFICATIONS=false +SKIP_RENDER_OPTIMIZATIONS=false +STORE_VARIABLES_FOR_LOCAL_MAPS=true + +# Debugging options +DEBUG_MODE=false +LOG_LEVEL=WARN + +# Internal URLs +API_URL=back:50051 + +RESTART_POLICY=unless-stopped diff --git a/contrib/docker/docker-compose.prod.yaml b/contrib/docker/docker-compose.prod.yaml index 62be6749..80ed192b 100644 --- a/contrib/docker/docker-compose.prod.yaml +++ b/contrib/docker/docker-compose.prod.yaml @@ -1,114 +1,128 @@ -version: "3.3" +version: "3.5" services: reverse-proxy: - image: traefik:v2.3 + image: traefik:v2.6 command: - - --log.level=WARN - #- --api.insecure=true + - --log.level=${LOG_LEVEL} - --providers.docker - - --entryPoints.web.address=:80 + # Entry points + - --entryPoints.web.address=:${HTTP_PORT} - --entrypoints.web.http.redirections.entryPoint.to=websecure - --entrypoints.web.http.redirections.entryPoint.scheme=https - - --entryPoints.websecure.address=:443 + - --entryPoints.websecure.address=:${HTTPS_PORT} + # HTTP challenge - --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL} - --certificatesresolvers.myresolver.acme.storage=/acme.json - # used during the challenge - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web + # Let's Encrypt's staging server + # uncomment during testing to avoid rate limiting + #- --certificatesresolvers.dnsresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory ports: - - "80:80" - - "443:443" - # The Web UI (enabled by --api.insecure=true) - #- "8080:8080" - depends_on: - - pusher - - front + - "${HTTP_PORT}:80" + - "${HTTPS_PORT}:443" volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./acme.json:/acme.json - restart: unless-stopped + - ${DATA_DIR}/letsencrypt/acme.json:/acme.json + restart: ${RESTART_POLICY} front: build: context: ../.. dockerfile: front/Dockerfile - #image: thecodingmachine/workadventure-front:master + #image: thecodingmachine/workadventure-front:${VERSION} environment: - DEBUG_MODE: "$DEBUG_MODE" - JITSI_URL: $JITSI_URL - JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" - PUSHER_URL: //pusher.${DOMAIN} - ICON_URL: //icon.${DOMAIN} - TURN_SERVER: "${TURN_SERVER}" - TURN_USER: "${TURN_USER}" - TURN_PASSWORD: "${TURN_PASSWORD}" - START_ROOM_URL: "${START_ROOM_URL}" + - DEBUG_MODE + - JITSI_URL + - JITSI_PRIVATE_MODE + - PUSHER_URL=//${PUSHER_HOST} + - ICON_URL=//${ICON_HOST} + - TURN_SERVER + - TURN_USER + - TURN_PASSWORD + - TURN_STATIC_AUTH_SECRET + - STUN_SERVER + - START_ROOM_URL + - SKIP_RENDER_OPTIMIZATIONS + - MAX_PER_GROUP + - MAX_USERNAME_LENGTH + - DISABLE_ANONYMOUS + - DISABLE_NOTIFICATIONS labels: - - "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)" - - "traefik.http.routers.front.entryPoints=web,traefik" + - "traefik.http.routers.front.rule=Host(`${FRONT_HOST}`)" + - "traefik.http.routers.front.entryPoints=web" - "traefik.http.services.front.loadbalancer.server.port=80" - - "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)" + - "traefik.http.routers.front-ssl.rule=Host(`${FRONT_HOST}`)" - "traefik.http.routers.front-ssl.entryPoints=websecure" - - "traefik.http.routers.front-ssl.tls=true" - "traefik.http.routers.front-ssl.service=front" + - "traefik.http.routers.front-ssl.tls=true" - "traefik.http.routers.front-ssl.tls.certresolver=myresolver" - restart: unless-stopped + restart: ${RESTART_POLICY} pusher: build: context: ../.. dockerfile: pusher/Dockerfile - #image: thecodingmachine/workadventure-pusher:master + #image: thecodingmachine/workadventure-pusher:${VERSION} command: yarn run runprod environment: - SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" - SECRET_KEY: yourSecretKey - API_URL: back:50051 - JITSI_URL: $JITSI_URL - JITSI_ISS: $JITSI_ISS - FRONT_URL: https://play.${DOMAIN} + - SECRET_JITSI_KEY + - SECRET_KEY + - API_URL + - FRONT_URL=https://${FRONT_HOST} + - JITSI_URL + - JITSI_ISS + - DISABLE_ANONYMOUS labels: - - "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)" - - "traefik.http.routers.pusher.entryPoints=web,traefik" + - "traefik.http.routers.pusher.rule=Host(`${PUSHER_HOST}`)" + - "traefik.http.routers.pusher.entryPoints=web" - "traefik.http.services.pusher.loadbalancer.server.port=8080" - - "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)" + - "traefik.http.routers.pusher-ssl.rule=Host(${PUSHER_HOST}`)" - "traefik.http.routers.pusher-ssl.entryPoints=websecure" - - "traefik.http.routers.pusher-ssl.tls=true" - "traefik.http.routers.pusher-ssl.service=pusher" + - "traefik.http.routers.pusher-ssl.tls=true" - "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver" - restart: unless-stopped + restart: ${RESTART_POLICY} back: build: context: ../.. dockerfile: back/Dockerfile - #image: thecodingmachine/workadventure-back:master + #image: thecodingmachine/workadventure-back:${VERSION} command: yarn run runprod environment: - SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" - ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" - ADMIN_API_URL: "$ADMIN_API_URL" - JITSI_URL: $JITSI_URL - JITSI_ISS: $JITSI_ISS + - SECRET_JITSI_KEY + - SECRET_KEY + - ADMIN_API_TOKEN + - ADMIN_API_URL + - TURN_SERVER + - TURN_USER + - TURN_PASSWORD + - TURN_STATIC_AUTH_SECRET + - STUN_SERVER + - JITSI_URL + - JITSI_ISS + - MAX_PER_GROUP + - STORE_VARIABLES_FOR_LOCAL_MAPS labels: - - "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)" + - "traefik.http.routers.back.rule=Host(`${BACK_HOST}`)" - "traefik.http.routers.back.entryPoints=web" - "traefik.http.services.back.loadbalancer.server.port=8080" - - "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)" + - "traefik.http.routers.back-ssl.rule=Host(`${BACK_HOST}`)" - "traefik.http.routers.back-ssl.entryPoints=websecure" - - "traefik.http.routers.back-ssl.tls=true" - "traefik.http.routers.back-ssl.service=back" + - "traefik.http.routers.back-ssl.tls=true" - "traefik.http.routers.back-ssl.tls.certresolver=myresolver" - restart: unless-stopped + restart: ${RESTART_POLICY} icon: image: matthiasluedtke/iconserver:v3.13.0 labels: - - "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)" + - "traefik.http.routers.icon.rule=Host(`${ICON_HOST}`)" - "traefik.http.routers.icon.entryPoints=web,traefik" - "traefik.http.services.icon.loadbalancer.server.port=8080" - - "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)" + - "traefik.http.routers.icon-ssl.rule=Host(`${ICON_HOST}`)" - "traefik.http.routers.icon-ssl.entryPoints=websecure" - - "traefik.http.routers.icon-ssl.tls=true" - "traefik.http.routers.icon-ssl.service=icon" + - "traefik.http.routers.icon-ssl.tls=true" - "traefik.http.routers.icon-ssl.tls.certresolver=myresolver" diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index 2743d1ad..8d9fe595 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -3,7 +3,7 @@ ### Opening a web page in a new tab -``` +```ts WA.nav.openTab(url: string): void ``` @@ -11,13 +11,13 @@ Opens the webpage at "url" in your browser, in a new tab. Example: -```javascript +```ts WA.nav.openTab('https://www.wikipedia.org/'); ``` ### Opening a web page in the current tab -``` +```ts WA.nav.goToPage(url: string): void ``` @@ -25,14 +25,13 @@ Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdvent Example: -```javascript +```ts WA.nav.goToPage('https://www.wikipedia.org/'); ``` ### Going to a different map from the script -``` - +```ts WA.nav.goToRoom(url: string): void ``` @@ -43,7 +42,7 @@ global urls: "/_/global/domain/path/map.json[#start-layer-name]" Example: -```javascript +```ts WA.nav.goToRoom("/@/tcm/workadventure/floor0") // workadventure urls WA.nav.goToRoom('../otherMap/map.json'); WA.nav.goToRoom("/_/global/.json#start-layer-2") @@ -51,25 +50,25 @@ WA.nav.goToRoom("/_/global/.json#start-layer-2") ### Opening/closing web page in Co-Websites -``` -WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number, closable: boolean, lazy: boolean): Promise +```ts +WA.nav.openCoWebSite(url: string, allowApi?: boolean = false, allowPolicy?: string = "", percentWidth?: number, position?: number, closable?: boolean, lazy?: boolean): Promise ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow),widthPercent define the width of the main cowebsite beetween the min size and the max size (70% of the viewport), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy it's to add the cowebsite but don't load it. Example: -```javascript +```ts const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/'); -const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1, true, true); +const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 70, 1, true, true); // ... coWebsite.close(); ``` ### Get all Co-Websites -``` +```ts WA.nav.getCoWebSites(): Promise ``` @@ -77,6 +76,6 @@ Get all opened co-websites with their ids and positions. Example: -```javascript +```ts const coWebsites = await WA.nav.getCowebSites(); ``` diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md index 2040beb3..25e530de 100644 --- a/docs/maps/entry-exit.md +++ b/docs/maps/entry-exit.md @@ -82,7 +82,11 @@ We are able to direct a Woka to the desired place immediately after spawn. To ma ``` .../my_map.json#moveTo=meeting-room&start ``` +*...or even like this!* +``` +.../my_map.json#start&moveTo=200,100 +``` -For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. +For this to work, moveTo must be equal to the x and y position, layer name, or object name of interest. Layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. ![](images/moveTo-layer-example.png) \ No newline at end of file diff --git a/docs/maps/images/icon_open_website.png b/docs/maps/images/icon_open_website.png new file mode 100644 index 00000000..f3a505bd Binary files /dev/null and b/docs/maps/images/icon_open_website.png differ diff --git a/docs/maps/opening-a-website.md b/docs/maps/opening-a-website.md index 64b19f1c..a84bde30 100644 --- a/docs/maps/opening-a-website.md +++ b/docs/maps/opening-a-website.md @@ -52,6 +52,13 @@ If you set `openWebsiteTrigger: onaction`, when the user walks on the layer, an If you set `openWebsiteTriggerMessage: your message action` you can edit alert message displayed. If is not defined, the default message displayed is 'Press on SPACE to open the web site'. +If you set `openWebsiteTrigger: onicon`, when the user walks on the layer, an icon will be displayed at the bottom of the screen: + +
+ +
The iFrame will only open if the user clicks on icon
+
+ ### Setting the iFrame "allow" attribute By default, iFrames have limited rights in browsers. For instance, they cannot put their content in fullscreen, they cannot start your webcam, etc... diff --git a/front/.prettierrc.json b/front/.prettierrc.json index 057ed062..e8980d15 100644 --- a/front/.prettierrc.json +++ b/front/.prettierrc.json @@ -1,5 +1,4 @@ { "printWidth": 120, - "tabWidth": 4, - "plugins": ["prettier-plugin-svelte"] + "tabWidth": 4 } diff --git a/front/package.json b/front/package.json index f9246112..4c673e29 100644 --- a/front/package.json +++ b/front/package.json @@ -61,7 +61,7 @@ "zod": "^3.11.6" }, "scripts": { - "start": "run-p templater serve watch-iframe-api svelte-check-watch typesafe-i18n", + "start": "run-p templater serve watch-iframe-api svelte-check-watch typesafe-i18n-watch", "templater": "cross-env ./templater.sh", "serve": "cross-env vite --host", "build": "cross-env vite build", @@ -77,7 +77,8 @@ "pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'", "pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'", "typecheck": "tsc --noEmit", - "typesafe-i18n": "typesafe-i18n --no-watch" + "typesafe-i18n": "typesafe-i18n --no-watch", + "typesafe-i18n-watch": "typesafe-i18n" }, "lint-staged": { "*.svelte": [ diff --git a/front/public/resources/logos/meet.svg b/front/public/resources/logos/meet.svg new file mode 100644 index 00000000..787134e1 --- /dev/null +++ b/front/public/resources/logos/meet.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts index 51a17763..b991d3f7 100644 --- a/front/src/Api/Events/OpenCoWebsiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts @@ -5,6 +5,7 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface() url: tg.isString, allowApi: tg.isOptional(tg.isBoolean), allowPolicy: tg.isOptional(tg.isString), + widthPercent: tg.isOptional(tg.isNumber), position: tg.isOptional(tg.isNumber), closable: tg.isOptional(tg.isBoolean), lazy: tg.isOptional(tg.isBoolean), diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index d5362b4b..b57f2456 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -45,6 +45,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution { - icon.src = `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`; - icon.alt = coWebsite.altMessage ?? urlObject.hostname; + icon.src = isJitsi + ? "/resources/logos/meet.svg" + : `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`; + icon.alt = coWebsite.getUrl().hostname; icon.onload = () => { iconLoaded = true; }; @@ -30,17 +33,24 @@ if (vertical) { coWebsiteManager.goToMain(coWebsite); } else if ($mainCoWebsite) { - if ($mainCoWebsite.iframe.id === coWebsite.iframe.id) { - const coWebsites = $coWebsitesNotAsleep; - const newMain = $highlightedEmbedScreen ?? coWebsites.length > 1 ? coWebsites[1] : undefined; - if (newMain) { - coWebsiteManager.goToMain(coWebsite); + if ($mainCoWebsite.getId() === coWebsite.getId()) { + if (coWebsiteManager.getMainState() === iframeStates.closed) { + coWebsiteManager.displayMain(); + } else if ($highlightedEmbedScreen?.type === "cowebsite") { + coWebsiteManager.goToMain($highlightedEmbedScreen.embed); + } else { + coWebsiteManager.hideMain(); } } else { - highlightedEmbedScreen.toggleHighlight({ - type: "cowebsite", - embed: coWebsite, - }); + if (coWebsiteManager.getMainState() === iframeStates.closed) { + coWebsiteManager.goToMain(coWebsite); + coWebsiteManager.displayMain(); + } else { + highlightedEmbedScreen.toggleHighlight({ + type: "cowebsite", + embed: coWebsite, + }); + } } } @@ -58,11 +68,14 @@ let isHighlight: boolean = false; let isMain: boolean = false; $: { - isMain = $mainCoWebsite !== undefined && $mainCoWebsite.iframe === coWebsite.iframe; + isMain = + $mainState === iframeStates.opened && + $mainCoWebsite !== undefined && + $mainCoWebsite.getId() === coWebsite.getId(); isHighlight = $highlightedEmbedScreen !== null && $highlightedEmbedScreen.type === "cowebsite" && - $highlightedEmbedScreen.embed.iframe === coWebsite.iframe; + $highlightedEmbedScreen.embed.getId() === coWebsite.getId(); } @@ -79,6 +92,7 @@ diff --git a/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte b/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte index f2db5384..ad0fcd44 100644 --- a/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte +++ b/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte @@ -7,7 +7,7 @@ {#if $coWebsites.length > 0}
- {#each [...$coWebsites.values()] as coWebsite, index (coWebsite.iframe.id)} + {#each [...$coWebsites.values()] as coWebsite, index (coWebsite.getId())} {/each}
diff --git a/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte b/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte index 25ff16c8..846a0432 100644 --- a/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte +++ b/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte @@ -23,8 +23,9 @@ {#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)} = 4} /> {/each} diff --git a/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte b/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte index f4a9f939..dbf7ee71 100644 --- a/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte +++ b/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte @@ -9,13 +9,11 @@ function closeCoWebsite() { if ($highlightedEmbedScreen?.type === "cowebsite") { - if ($highlightedEmbedScreen.embed.closable) { - coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed).catch(() => { - console.error("Error during co-website highlighted closing"); - }); + if ($highlightedEmbedScreen.embed.isClosable()) { + coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed); } else { - coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch(() => { - console.error("Error during co-website highlighted unloading"); + coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch((err) => { + console.error("Cannot unload co-website", err); }); } } @@ -68,9 +66,9 @@ /> {/key} {:else if $highlightedEmbedScreen.type === "cowebsite"} - {#key $highlightedEmbedScreen.embed.iframe.id} + {#key $highlightedEmbedScreen.embed.getId()}
diff --git a/front/src/Components/Login/LoginScene.svelte b/front/src/Components/Login/LoginScene.svelte index ea98e7b6..6b26e659 100644 --- a/front/src/Components/Login/LoginScene.svelte +++ b/front/src/Components/Login/LoginScene.svelte @@ -53,7 +53,7 @@
Need for traduction

- {$LL.login.terms()} + {@html $LL.login.terms()}

{/if} diff --git a/front/src/Components/Menu/GuestSubMenu.svelte b/front/src/Components/Menu/GuestSubMenu.svelte index a0cd94d7..fbbc48a4 100644 --- a/front/src/Components/Menu/GuestSubMenu.svelte +++ b/front/src/Components/Menu/GuestSubMenu.svelte @@ -1,5 +1,12 @@ @@ -16,6 +17,7 @@
@@ -66,6 +68,11 @@ } } + &.mozaic-solo { + max-height: inherit !important; + width: 90% !important; + } + &.mozaic-full-width { width: 95%; max-width: 95%; @@ -73,6 +80,7 @@ margin-right: 3%; margin-top: auto; margin-bottom: auto; + max-height: 95%; &:hover { margin-top: auto; @@ -85,6 +93,7 @@ max-width: 95%; margin-top: auto; margin-bottom: auto; + max-height: 95%; &:hover { margin-top: auto; diff --git a/front/src/Components/WarningContainer/WarningContainer.svelte b/front/src/Components/WarningContainer/WarningContainer.svelte index c538aec5..ebc47639 100644 --- a/front/src/Components/WarningContainer/WarningContainer.svelte +++ b/front/src/Components/WarningContainer/WarningContainer.svelte @@ -38,7 +38,6 @@ right: 0; margin-left: auto; margin-right: auto; - transform: translate(-50%, 0); font-family: Lato; min-width: 300px; opacity: 0.9; diff --git a/front/src/Interfaces/UserInputHandlerInterface.ts b/front/src/Interfaces/UserInputHandlerInterface.ts index cf7b2f1c..2a8c6b3e 100644 --- a/front/src/Interfaces/UserInputHandlerInterface.ts +++ b/front/src/Interfaces/UserInputHandlerInterface.ts @@ -9,4 +9,7 @@ export interface UserInputHandlerInterface { handlePointerUpEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void; handlePointerDownEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void; handleSpaceKeyUpEvent: (event: Event) => Event; + + addSpaceEventListener: (callback: Function) => void; + removeSpaceEventListner: (callback: Function) => void; } diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 6157ebaa..1a69c879 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -4,6 +4,7 @@ import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Anima import { TexturesHelper } from "../Helpers/TexturesHelper"; import { Writable, writable } from "svelte/store"; import type { PictureStore } from "../../Stores/PictureStore"; +import type CancelablePromise from "cancelable-promise"; export interface CompanionStatus { x: number; @@ -25,8 +26,9 @@ export class Companion extends Container { private direction: PlayerAnimationDirections; private animationType: PlayerAnimationTypes; private readonly _pictureStore: Writable; + private texturePromise: CancelablePromise | undefined; - constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise) { + constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: CancelablePromise) { super(scene, x + 14, y + 4); this.sprites = new Map(); @@ -41,7 +43,7 @@ export class Companion extends Container { this.companionName = name; this._pictureStore = writable(undefined); - texturePromise + this.texturePromise = texturePromise .then((resource) => { this.addResource(resource); this.invisible = false; @@ -234,6 +236,7 @@ export class Companion extends Container { } public destroy(): void { + this.texturePromise?.cancel(); for (const sprite of this.sprites.values()) { if (this.scene) { this.scene.sys.updateList.remove(sprite); diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index 98cceafa..fee51ca3 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -1,5 +1,6 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; +import CancelablePromise from "cancelable-promise"; export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { @@ -9,8 +10,12 @@ export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourc return COMPANION_RESOURCES; }; -export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise => { - return new Promise((resolve, reject) => { +export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): CancelablePromise => { + return new CancelablePromise((resolve, reject, cancel) => { + cancel(() => { + return; + }); + const resource = COMPANION_RESOURCES.find((item) => item.name === name); if (typeof resource === "undefined") { diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 4465a6b6..b411eee7 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -59,7 +59,7 @@ export abstract class Character extends Container implements OutlineableInterfac frame: string | number, isClickable: boolean, companion: string | null, - companionTexturePromise?: Promise + companionTexturePromise?: CancelablePromise ) { super(scene, x, y /*, texture, frame*/); this.scene = scene; @@ -142,7 +142,7 @@ export abstract class Character extends Container implements OutlineableInterfac this.clickable = clickable; if (clickable) { this.setInteractive({ - hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius), + hitArea: new Phaser.Geom.Circle(8, 8, interactiveRadius), hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method useHandCursor: true, }); @@ -159,6 +159,27 @@ export abstract class Character extends Container implements OutlineableInterfac return { x: this.x, y: this.y }; } + /** + * Returns position based on where player is currently facing + * @param shift How far from player should the point of interest be. + */ + public getDirectionalActivationPosition(shift: number): { x: number; y: number } { + switch (this.lastDirection) { + case PlayerAnimationDirections.Down: { + return { x: this.x, y: this.y + shift }; + } + case PlayerAnimationDirections.Left: { + return { x: this.x - shift, y: this.y }; + } + case PlayerAnimationDirections.Right: { + return { x: this.x + shift, y: this.y }; + } + case PlayerAnimationDirections.Up: { + return { x: this.x, y: this.y - shift }; + } + } + } + public getObjectToOutline(): Phaser.GameObjects.GameObject { return this.playerNameText; } @@ -179,7 +200,7 @@ export abstract class Character extends Container implements OutlineableInterfac }); } - public addCompanion(name: string, texturePromise?: Promise): void { + public addCompanion(name: string, texturePromise?: CancelablePromise): void { if (typeof texturePromise !== "undefined") { this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise); } @@ -455,16 +476,16 @@ export abstract class Character extends Container implements OutlineableInterfac this.outlineColorStore.removeApiColor(); } - public pointerOverOutline(): void { - this.outlineColorStore.pointerOver(); + public pointerOverOutline(color: number): void { + this.outlineColorStore.pointerOver(color); } public pointerOutOutline(): void { this.outlineColorStore.pointerOut(); } - public characterCloseByOutline(): void { - this.outlineColorStore.characterCloseBy(); + public characterCloseByOutline(color: number): void { + this.outlineColorStore.characterCloseBy(color); } public characterFarAwayOutline(): void { diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index df496896..7af6da2e 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -31,18 +31,18 @@ export class RemotePlayer extends Character implements ActivatableInterface { moving: boolean, visitCardUrl: string | null, companion: string | null, - companionTexturePromise?: Promise, + companionTexturePromise?: CancelablePromise, activationRadius?: number ) { super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise); //set data this.userId = userId; + this.visitCardUrl = visitCardUrl; this.registeredActions = []; this.registerDefaultActionsMenuActions(); this.setClickable(this.registeredActions.length > 0); this.activationRadius = activationRadius ?? 96; - this.visitCardUrl = visitCardUrl; this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => { this.isActionsMenuInitialized = value ? true : false; }); @@ -118,7 +118,7 @@ export class RemotePlayer extends Character implements ActivatableInterface { private bindEventHandlers(): void { this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => { - if (event.downElement.nodeName === "CANVAS") { + if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) { this.toggleActionsMenu(); } }); diff --git a/front/src/Phaser/Game/ActivatablesManager.ts b/front/src/Phaser/Game/ActivatablesManager.ts index 60e967d9..74c637d7 100644 --- a/front/src/Phaser/Game/ActivatablesManager.ts +++ b/front/src/Phaser/Game/ActivatablesManager.ts @@ -11,6 +11,11 @@ export class ActivatablesManager { private currentPlayer: Player; + private canSelectByDistance: boolean = true; + + private readonly outlineColor = 0xffff00; + private readonly directionalActivationPositionShift = 50; + constructor(currentPlayer: Player) { this.currentPlayer = currentPlayer; } @@ -27,7 +32,7 @@ export class ActivatablesManager { } this.selectedActivatableObjectByPointer = object; if (isOutlineable(this.selectedActivatableObjectByPointer)) { - this.selectedActivatableObjectByPointer?.pointerOverOutline(); + this.selectedActivatableObjectByPointer?.pointerOverOutline(this.outlineColor); } } @@ -37,7 +42,7 @@ export class ActivatablesManager { } this.selectedActivatableObjectByPointer = undefined; if (isOutlineable(this.selectedActivatableObjectByDistance)) { - this.selectedActivatableObjectByDistance?.characterCloseByOutline(); + this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor); } } @@ -46,6 +51,9 @@ export class ActivatablesManager { } public deduceSelectedActivatableObjectByDistance(): void { + if (!this.canSelectByDistance) { + return; + } const newNearestObject = this.findNearestActivatableObject(); if (this.selectedActivatableObjectByDistance === newNearestObject) { return; @@ -60,10 +68,42 @@ export class ActivatablesManager { } this.selectedActivatableObjectByDistance = newNearestObject; if (isOutlineable(this.selectedActivatableObjectByDistance)) { - this.selectedActivatableObjectByDistance?.characterCloseByOutline(); + this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor); } } + public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void { + const currentPlayerPos = this.currentPlayer.getDirectionalActivationPosition( + this.directionalActivationPositionShift + ); + for (const object of objects) { + const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition()); + this.activatableObjectsDistances.set(object, distance); + } + } + + public updateDistanceForSingleActivatableObject(object: ActivatableInterface): void { + this.activatableObjectsDistances.set( + object, + MathUtils.distanceBetween( + this.currentPlayer.getDirectionalActivationPosition(this.directionalActivationPositionShift), + object.getPosition() + ) + ); + } + + public disableSelectingByDistance(): void { + this.canSelectByDistance = false; + if (isOutlineable(this.selectedActivatableObjectByDistance)) { + this.selectedActivatableObjectByDistance?.characterFarAwayOutline(); + } + this.selectedActivatableObjectByDistance = undefined; + } + + public enableSelectingByDistance(): void { + this.canSelectByDistance = true; + } + private findNearestActivatableObject(): ActivatableInterface | undefined { let shortestDistance: number = Infinity; let closestObject: ActivatableInterface | undefined = undefined; @@ -76,18 +116,8 @@ export class ActivatablesManager { } return closestObject; } - public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void { - const currentPlayerPos = this.currentPlayer.getPosition(); - for (const object of objects) { - const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition()); - this.activatableObjectsDistances.set(object, distance); - } - } - public updateDistanceForSingleActivatableObject(object: ActivatableInterface): void { - this.activatableObjectsDistances.set( - object, - MathUtils.distanceBetween(this.currentPlayer.getPosition(), object.getPosition()) - ); + public isSelectingByDistanceEnabled(): boolean { + return this.canSelectByDistance; } } diff --git a/front/src/Phaser/Game/Game.ts b/front/src/Phaser/Game/Game.ts index 865026f7..783f2348 100644 --- a/front/src/Phaser/Game/Game.ts +++ b/front/src/Phaser/Game/Game.ts @@ -26,15 +26,6 @@ export class Game extends Phaser.Game { } } }); - - /*window.addEventListener('resize', (event) => { - // Let's trigger the onResize method of any active scene that is a ResizableScene - for (const scene of this.scene.getScenes(true)) { - if (scene instanceof ResizableScene) { - scene.onResize(event); - } - } - });*/ } public step(time: number, delta: number) { diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 54f91866..3df3cb01 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -85,6 +85,7 @@ export class GameMap { phaserMap .createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32) .setDepth(depth) + .setScrollFactor(layer.parallaxx ?? 1, layer.parallaxy ?? 1) .setAlpha(layer.opacity) .setVisible(layer.visible) .setSize(layer.width, layer.height) @@ -120,7 +121,7 @@ export class GameMap { return []; } - public getCollisionsGrid(): number[][] { + public getCollisionGrid(): number[][] { const grid: number[][] = []; for (let y = 0; y < this.map.height; y += 1) { const row: number[] = []; @@ -322,12 +323,19 @@ export class GameMap { throw new Error("No possible position found"); } + public getObjectWithName(name: string): ITiledMapObject | undefined { + return this.tiledObjects.find((object) => object.name === name); + } + private getLayersByKey(key: number): Array { return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0); } private isCollidingAt(x: number, y: number): boolean { for (const layer of this.phaserLayers) { + if (!layer.visible) { + continue; + } if (layer.getTileAt(x, y)?.properties[GameMapProperties.COLLIDES]) { return true; } diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index 56627ec3..b77bd02b 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -20,6 +20,7 @@ export enum GameMapProperties { OPEN_WEBSITE = "openWebsite", OPEN_WEBSITE_ALLOW_API = "openWebsiteAllowApi", OPEN_WEBSITE_POLICY = "openWebsitePolicy", + OPEN_WEBSITE_WIDTH = "openWebsiteWidth", OPEN_WEBSITE_POSITION = "openWebsitePosition", OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger", OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage", diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index caa83cb0..103cc4bc 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -1,33 +1,36 @@ import type { GameScene } from "./GameScene"; import type { GameMap } from "./GameMap"; import { scriptUtils } from "../../Api/ScriptUtils"; -import type { CoWebsite } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { get } from "svelte/store"; -import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager"; +import { ON_ACTION_TRIGGER_BUTTON, ON_ICON_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager"; import type { ITiledMapLayer } from "../Map/ITiledMap"; import { GameMapProperties } from "./GameMapProperties"; -import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; - -enum OpenCoWebsiteState { - ASLEEP, - OPENED, - MUST_BE_CLOSE, -} +import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite"; +import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; +import { jitsiFactory } from "../../WebRtc/JitsiFactory"; +import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable"; +import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; +import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore"; +import { iframeListener } from "../../Api/IframeListener"; +import { Room } from "../../Connexion/Room"; +import LL from "../../i18n/i18n-svelte"; interface OpenCoWebsite { - coWebsite: CoWebsite; - state: OpenCoWebsiteState; + actionId: string; + coWebsite?: CoWebsite; } export class GameMapPropertiesListener { private coWebsitesOpenByLayer = new Map(); + private coWebsitesActionTriggerByLayer = new Map(); constructor(private scene: GameScene, private gameMap: GameMap) {} register() { + // Website on new tab this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => { if (newValue === undefined) { layoutManagerActionStore.removeAction("openTab"); @@ -38,7 +41,7 @@ export class GameMapPropertiesListener { if (forceTrigger || openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE); if (message === undefined) { - message = "Press SPACE or touch here to open web site in new tab"; + message = get(LL).trigger.newTab(); } layoutManagerActionStore.addAction({ uuid: "openTab", @@ -53,6 +56,129 @@ export class GameMapPropertiesListener { } }); + // Jitsi room + this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => { + if (newValue === undefined) { + layoutManagerActionStore.removeAction("jitsi"); + coWebsiteManager.getCoWebsites().forEach((coWebsite) => { + if (coWebsite instanceof JitsiCoWebsite) { + coWebsiteManager.closeCoWebsite(coWebsite); + } + }); + } else { + const openJitsiRoomFunction = () => { + const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.instance); + const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; + + if (JITSI_PRIVATE_MODE && !jitsiUrl) { + const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined; + + this.scene.connection?.emitQueryJitsiJwtMessage(roomName, adminTag); + } else { + let domain = jitsiUrl || JITSI_URL; + if (domain === undefined) { + throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); + } + + if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") { + domain = `${location.protocol}//${domain}`; + } + + const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false); + + coWebsiteManager.addCoWebsiteToStore(coWebsite, 0); + this.scene.initialiseJitsi(coWebsite, roomName, undefined); + } + layoutManagerActionStore.removeAction("jitsi"); + }; + + const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER); + const forceTrigger = localUserStore.getForceCowebsiteTrigger(); + if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { + let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE); + if (message === undefined) { + message = get(LL).trigger.jitsiRoom(); + } + layoutManagerActionStore.addAction({ + uuid: "jitsi", + type: "message", + message: message, + callback: () => openJitsiRoomFunction(), + userInputManager: this.scene.userInputManager, + }); + } else { + openJitsiRoomFunction(); + } + } + }); + + this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => { + if (newValue) { + this.scene + .onMapExit( + Room.getRoomPathFromExitSceneUrl( + newValue as string, + window.location.toString(), + this.scene.MapUrlFile + ) + ) + .catch((e) => console.error(e)); + } else { + setTimeout(() => { + layoutManagerActionStore.removeAction("roomAccessDenied"); + }, 2000); + } + }); + + this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => { + if (newValue) { + this.scene + .onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())) + .catch((e) => console.error(e)); + } else { + setTimeout(() => { + layoutManagerActionStore.removeAction("roomAccessDenied"); + }, 2000); + } + }); + + this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => { + if (newValue === undefined || newValue === false || newValue === "") { + this.scene.connection?.setSilent(false); + this.scene.CurrentPlayer.noSilent(); + } else { + this.scene.connection?.setSilent(true); + this.scene.CurrentPlayer.isSilent(); + } + }); + + this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => { + const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined; + const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined; + newValue === undefined + ? audioManagerFileStore.unloadAudio() + : audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), volume, loop); + audioManagerVisibilityStore.set(!(newValue === undefined)); + }); + + // TODO: This legacy property should be removed at some point + this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => { + newValue === undefined + ? audioManagerFileStore.unloadAudio() + : audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true); + audioManagerVisibilityStore.set(!(newValue === undefined)); + }); + + // TODO: Legacy functionnality replace by layer change + this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => { + if (oldValue) { + iframeListener.sendLeaveEvent(oldValue as string); + } + if (newValue) { + iframeListener.sendEnterEvent(newValue as string); + } + }); + // Open a new co-website by the property. this.gameMap.onEnterLayer((newLayers) => { const handler = () => { @@ -64,6 +190,7 @@ export class GameMapPropertiesListener { let openWebsiteProperty: string | undefined; let allowApiProperty: boolean | undefined; let websitePolicyProperty: string | undefined; + let websiteWidthProperty: number | undefined; let websitePositionProperty: number | undefined; let websiteTriggerProperty: string | undefined; let websiteTriggerMessageProperty: string | undefined; @@ -79,6 +206,9 @@ export class GameMapPropertiesListener { case GameMapProperties.OPEN_WEBSITE_POLICY: websitePolicyProperty = property.value as string | undefined; break; + case GameMapProperties.OPEN_WEBSITE_WIDTH: + websiteWidthProperty = property.value as number | undefined; + break; case GameMapProperties.OPEN_WEBSITE_POSITION: websitePositionProperty = property.value as number | undefined; break; @@ -95,55 +225,75 @@ export class GameMapPropertiesListener { return; } - const actionUuid = "openWebsite-" + (Math.random() + 1).toString(36).substring(7); + const actionId = "openWebsite-" + (Math.random() + 1).toString(36).substring(7); if (this.coWebsitesOpenByLayer.has(layer)) { return; } - const coWebsite = coWebsiteManager.addCoWebsite( - openWebsiteProperty, - this.scene.MapUrlFile, - allowApiProperty, - websitePolicyProperty, - websitePositionProperty, - false - ); + const coWebsiteOpen: OpenCoWebsite = { + actionId: actionId, + }; - this.coWebsitesOpenByLayer.set(layer, { - coWebsite: coWebsite, - state: OpenCoWebsiteState.ASLEEP, - }); + this.coWebsitesOpenByLayer.set(layer, coWebsiteOpen); - const openWebsiteFunction = () => { - coWebsiteManager - .loadCoWebsite(coWebsite) - .then((coWebsite) => { - const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); - if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { - coWebsiteManager.closeCoWebsite(coWebsite).catch(() => { - console.error("Error during a co-website closing"); - }); - this.coWebsitesOpenByLayer.delete(layer); - } else { - this.coWebsitesOpenByLayer.set(layer, { - coWebsite, - state: OpenCoWebsiteState.OPENED, - }); - } - }) - .catch(() => { - console.error("Error during loading a co-website: " + coWebsite.url); - }); + const loadCoWebsiteFunction = (coWebsite: CoWebsite) => { + coWebsiteManager.loadCoWebsite(coWebsite).catch(() => { + console.error("Error during loading a co-website: " + coWebsite.getUrl()); + }); - layoutManagerActionStore.removeAction(actionUuid); + layoutManagerActionStore.removeAction(actionId); + }; + + const openCoWebsiteFunction = () => { + const coWebsite = new SimpleCoWebsite( + new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile), + allowApiProperty, + websitePolicyProperty, + websiteWidthProperty, + false + ); + + coWebsiteOpen.coWebsite = coWebsite; + + coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty); + + loadCoWebsiteFunction(coWebsite); }; if ( - !localUserStore.getForceCowebsiteTrigger() && - websiteTriggerProperty !== ON_ACTION_TRIGGER_BUTTON + localUserStore.getForceCowebsiteTrigger() || + websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON ) { - openWebsiteFunction(); + if (!websiteTriggerMessageProperty) { + websiteTriggerMessageProperty = get(LL).trigger.cowebsite(); + } + + this.coWebsitesActionTriggerByLayer.set(layer, actionId); + + layoutManagerActionStore.addAction({ + uuid: actionId, + type: "message", + message: websiteTriggerMessageProperty, + callback: () => openCoWebsiteFunction(), + userInputManager: this.scene.userInputManager, + }); + } else if (websiteTriggerProperty === ON_ICON_TRIGGER_BUTTON) { + const coWebsite = new SimpleCoWebsite( + new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile), + allowApiProperty, + websitePolicyProperty, + websiteWidthProperty, + false + ); + + coWebsiteOpen.coWebsite = coWebsite; + + coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty); + } + + if (!websiteTriggerProperty) { + openCoWebsiteFunction(); } }); }; @@ -183,15 +333,35 @@ export class GameMapPropertiesListener { return; } - if (coWebsiteOpen.state === OpenCoWebsiteState.ASLEEP) { - coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE; - } + const coWebsite = coWebsiteOpen.coWebsite; - if (coWebsiteOpen.coWebsite !== undefined) { - coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e)); + if (coWebsite) { + coWebsiteManager.closeCoWebsite(coWebsite); } this.coWebsitesOpenByLayer.delete(layer); + + if (!websiteTriggerProperty) { + return; + } + + const actionStore = get(layoutManagerActionStore); + const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer); + + if (!actionTriggerUuid) { + return; + } + + const action = + actionStore && actionStore.length > 0 + ? actionStore.find((action) => action.uuid === actionTriggerUuid) + : undefined; + + if (action) { + layoutManagerActionStore.removeAction(actionTriggerUuid); + } + + this.coWebsitesActionTriggerByLayer.delete(layer); }); }; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a52c413c..5c8826bc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -5,7 +5,7 @@ import { get, Unsubscriber } from "svelte/store"; import { userMessageManager } from "../../Administration/UserMessageManager"; import { connectionManager } from "../../Connexion/ConnectionManager"; -import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; +import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { urlManager } from "../../Url/UrlManager"; import { mediaManager } from "../../WebRtc/MediaManager"; import { UserInputManager } from "../UserInput/UserInputManager"; @@ -20,9 +20,8 @@ import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; -import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager"; import { iframeListener } from "../../Api/IframeListener"; -import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; +import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; import { Room } from "../../Connexion/Room"; import { jitsiFactory } from "../../WebRtc/JitsiFactory"; @@ -76,7 +75,7 @@ import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore"; import { userIsAdminStore } from "../../Stores/GameStore"; import { contactPageStore } from "../../Stores/MenuStore"; import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; -import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore"; +import { audioManagerFileStore } from "../../Stores/AudioManagerStore"; import EVENT_TYPE = Phaser.Scenes.Events; import Texture = Phaser.Textures.Texture; @@ -92,6 +91,11 @@ import { MapStore } from "../../Stores/Utils/MapStore"; import { followUsersColorStore } from "../../Stores/FollowStore"; import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler"; import { locale } from "../../i18n/i18n-svelte"; +import { StringUtils } from "../../Utils/StringUtils"; +import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore"; +import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; +import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; +import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite"; export interface GameSceneInitInterface { initPosition: PointInterface | null; reconnecting: boolean; @@ -540,6 +544,8 @@ export class GameScene extends DirtyScene { urlManager.getStartLayerNameFromUrl() ); + startLayerNamesStore.set(this.startPositionCalculator.getStartPositionNames()); + //add entities this.Objects = new Array(); @@ -559,7 +565,7 @@ export class GameScene extends DirtyScene { this.pathfindingManager = new PathfindingManager( this, - this.gameMap.getCollisionsGrid(), + this.gameMap.getCollisionGrid(), this.gameMap.getTileDimensions() ); @@ -567,6 +573,8 @@ export class GameScene extends DirtyScene { this.createCurrentPlayer(); this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted + this.tryMovePlayerWithMoveToParameter(); + this.cameraManager = new CameraManager( this, { x: this.Map.widthInPixels, y: this.Map.heightInPixels }, @@ -575,7 +583,7 @@ export class GameScene extends DirtyScene { this.pathfindingManager = new PathfindingManager( this, - this.gameMap.getCollisionsGrid(), + this.gameMap.getCollisionGrid(), this.gameMap.getTileDimensions() ); @@ -626,7 +634,6 @@ export class GameScene extends DirtyScene { ); new GameMapPropertiesListener(this, this.gameMap).register(); - this.triggerOnMapLayerPropertyChange(); if (!this.room.isDisconnected()) { this.scene.sleep(); @@ -793,7 +800,19 @@ export class GameScene extends DirtyScene { * Triggered when we receive the JWT token to connect to Jitsi */ this.connection.sendJitsiJwtMessageStream.subscribe((message) => { - this.startJitsi(message.jitsiRoom, message.jwt); + if (!JITSI_URL) { + throw new Error("Missing JITSI_URL environment variable."); + } + + let domain = JITSI_URL; + + if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") { + domain = `${location.protocol}//${domain}`; + } + + const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false); + coWebsiteManager.addCoWebsiteToStore(coWebsite, 0); + this.initialiseJitsi(coWebsite, message.jitsiRoom, message.jwt); }); this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => { @@ -930,103 +949,6 @@ export class GameScene extends DirtyScene { } } - private triggerOnMapLayerPropertyChange() { - this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => { - if (newValue) { - this.onMapExit( - Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile) - ).catch((e) => console.error(e)); - } else { - setTimeout(() => { - layoutManagerActionStore.removeAction("roomAccessDenied"); - }, 2000); - } - }); - this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => { - if (newValue) { - this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) => - console.error(e) - ); - } else { - setTimeout(() => { - layoutManagerActionStore.removeAction("roomAccessDenied"); - }, 2000); - } - }); - - this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => { - if (newValue === undefined) { - layoutManagerActionStore.removeAction("jitsi"); - this.stopJitsi(); - } else { - const openJitsiRoomFunction = () => { - const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance); - const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; - if (JITSI_PRIVATE_MODE && !jitsiUrl) { - const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined; - - this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag); - } else { - this.startJitsi(roomName, undefined); - } - layoutManagerActionStore.removeAction("jitsi"); - }; - - const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER); - const forceTrigger = localUserStore.getForceCowebsiteTrigger(); - if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { - let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE); - if (message === undefined) { - message = "Press SPACE or touch here to enter Jitsi Meet room"; - } - layoutManagerActionStore.addAction({ - uuid: "jitsi", - type: "message", - message: message, - callback: () => openJitsiRoomFunction(), - userInputManager: this.userInputManager, - }); - } else { - openJitsiRoomFunction(); - } - } - }); - this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => { - if (newValue === undefined || newValue === false || newValue === "") { - this.connection?.setSilent(false); - this.CurrentPlayer.noSilent(); - } else { - this.connection?.setSilent(true); - this.CurrentPlayer.isSilent(); - } - }); - this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => { - const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined; - const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined; - newValue === undefined - ? audioManagerFileStore.unloadAudio() - : audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), volume, loop); - audioManagerVisibilityStore.set(!(newValue === undefined)); - }); - // TODO: This legacy property should be removed at some point - this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => { - newValue === undefined - ? audioManagerFileStore.unloadAudio() - : audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), undefined, true); - audioManagerVisibilityStore.set(!(newValue === undefined)); - }); - - // TODO: Legacy functionnality replace by layer change - this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => { - if (oldValue) { - iframeListener.sendLeaveEvent(oldValue as string); - } - if (newValue) { - iframeListener.sendEnterEvent(newValue as string); - } - }); - } - private listenToIframeEvents(): void { this.iframeSubscriptionList = []; this.iframeSubscriptionList.push( @@ -1259,12 +1181,11 @@ ${escapedMessage} throw new Error("Unknown query source"); } - const coWebsite = coWebsiteManager.addCoWebsite( - openCoWebsite.url, - iframeListener.getBaseUrlFromSource(source), + const coWebsite: SimpleCoWebsite = new SimpleCoWebsite( + new URL(openCoWebsite.url, iframeListener.getBaseUrlFromSource(source)), openCoWebsite.allowApi, openCoWebsite.allowPolicy, - openCoWebsite.position, + openCoWebsite.widthPercent, openCoWebsite.closable ?? true ); @@ -1273,7 +1194,7 @@ ${escapedMessage} } return { - id: coWebsite.iframe.id, + id: coWebsite.getId(), }; }); @@ -1282,27 +1203,23 @@ ${escapedMessage} return coWebsites.map((coWebsite: CoWebsite) => { return { - id: coWebsite.iframe.id, + id: coWebsite.getId(), }; }); }); - iframeListener.registerAnswerer("closeCoWebsite", async (coWebsiteId) => { + iframeListener.registerAnswerer("closeCoWebsite", (coWebsiteId) => { const coWebsite = coWebsiteManager.getCoWebsiteById(coWebsiteId); if (!coWebsite) { throw new Error("Unknown co-website"); } - return coWebsiteManager.closeCoWebsite(coWebsite).catch((error) => { - throw new Error("Error on closing co-website"); - }); + return coWebsiteManager.closeCoWebsite(coWebsite); }); - iframeListener.registerAnswerer("closeCoWebsites", async () => { - return await coWebsiteManager.closeCoWebsites().catch((error) => { - throw new Error("Error on closing all co-websites"); - }); + iframeListener.registerAnswerer("closeCoWebsites", () => { + return coWebsiteManager.closeCoWebsites(); }); iframeListener.registerAnswerer("getMapData", () => { @@ -1387,7 +1304,7 @@ ${escapedMessage} //Create new colliders with the new GameMap this.createCollisionWithPlayer(); //Create new trigger with the new GameMap - this.triggerOnMapLayerPropertyChange(); + new GameMapPropertiesListener(this, this.gameMap).register(); resolve(newFirstgid); }); }); @@ -1458,9 +1375,9 @@ ${escapedMessage} }); iframeListener.registerAnswerer("movePlayerTo", async (message) => { - const index = this.getGameMap().getTileIndexAt(message.x, message.y); - const startTile = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y); - const path = await this.getPathfindingManager().findPath(startTile, index, true, true); + const startTileIndex = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y); + const destinationTileIndex = this.getGameMap().getTileIndexAt(message.x, message.y); + const path = await this.getPathfindingManager().findPath(startTileIndex, destinationTileIndex, true, true); path.shift(); if (path.length === 0) { throw new Error("no path available"); @@ -1500,14 +1417,15 @@ ${escapedMessage} phaserLayers[i].setCollisionByProperty({ collides: true }, visible); } } + this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid()); this.markDirty(); } - private getMapDirUrl(): string { - return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/")); + public getMapDirUrl(): string { + return this.MapUrlFile.substring(0, this.MapUrlFile.lastIndexOf("/")); } - private async onMapExit(roomUrl: URL) { + public async onMapExit(roomUrl: URL) { if (this.mapTransitioning) return; this.mapTransitioning = true; @@ -1566,14 +1484,13 @@ ${escapedMessage} public cleanupClosingScene(): void { // stop playing audio, close any open website, stop any open Jitsi - coWebsiteManager.closeCoWebsites().catch((e) => console.error(e)); + coWebsiteManager.closeCoWebsites(); // Stop the script, if any const scripts = this.getScriptUrls(this.mapFile); for (const script of scripts) { iframeListener.unregisterScript(script); } - this.stopJitsi(); audioManagerFileStore.unloadAudio(); // We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map. this.connection?.closeConnection(); @@ -1624,6 +1541,36 @@ ${escapedMessage} this.MapPlayersByKey.clear(); } + private tryMovePlayerWithMoveToParameter(): void { + const moveToParam = urlManager.getHashParameter("moveTo"); + if (moveToParam) { + try { + let endPos; + const posFromParam = StringUtils.parsePointFromParam(moveToParam); + if (posFromParam) { + endPos = this.gameMap.getTileIndexAt(posFromParam.x, posFromParam.y); + } else { + const destinationObject = this.gameMap.getObjectWithName(moveToParam); + if (destinationObject) { + endPos = this.gameMap.getTileIndexAt(destinationObject.x, destinationObject.y); + } else { + endPos = this.gameMap.getRandomPositionFromLayer(moveToParam); + } + } + this.pathfindingManager + .findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos) + .then((path) => { + if (path && path.length > 0) { + this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason)); + } + }) + .catch((reason) => console.warn(reason)); + } catch (err) { + console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`); + } + } + } + private getExitUrl(layer: ITiledMapLayer): string | undefined { return this.getProperty(layer, GameMapProperties.EXIT_URL) as string | undefined; } @@ -1736,26 +1683,16 @@ ${escapedMessage} emoteMenuStore.openEmoteMenu(); } }); + this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => { + this.CurrentPlayer.pointerOverOutline(0x00ffff); + }); + this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => { + this.CurrentPlayer.pointerOutOutline(); + }); this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => { this.connection?.emitEmoteEvent(emoteKey); analyticsClient.launchEmote(emoteKey); }); - const moveToParam = urlManager.getHashParameter("moveTo"); - if (moveToParam) { - try { - const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam); - this.pathfindingManager - .findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos) - .then((path) => { - if (path && path.length > 0) { - this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason)); - } - }) - .catch((reason) => console.warn(reason)); - } catch (err) { - console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`); - } - } } catch (err) { if (err instanceof TextureError) { gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); @@ -2108,7 +2045,7 @@ ${escapedMessage} mediaManager.hideMyCamera(); } - public startJitsi(roomName: string, jwt?: string): void { + public initialiseJitsi(coWebsite: JitsiCoWebsite, roomName: string, jwt?: string): void { const allProps = this.gameMap.getCurrentProperties(); const jitsiConfig = this.safeParseJSONstring( allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined, @@ -2120,20 +2057,15 @@ ${escapedMessage} ); const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; - jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl).catch(() => { - console.error("Cannot start a Jitsi co-website"); + coWebsite.setJitsiLoadPromise(() => { + return jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl); }); - this.disableMediaBehaviors(); - analyticsClient.enteredJitsi(roomName, this.room.id); - } - public stopJitsi(): void { - const coWebsite = coWebsiteManager.searchJitsi(); - if (coWebsite) { - coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => { - console.error("Error during Jitsi co-website closing", e); - }); - } + coWebsiteManager.loadCoWebsite(coWebsite).catch((err) => { + console.error(err); + }); + + analyticsClient.enteredJitsi(roomName, this.room.id); } //todo: put this into an 'orchestrator' scene (EntryScene?) diff --git a/front/src/Phaser/Game/OutlineableInterface.ts b/front/src/Phaser/Game/OutlineableInterface.ts index bee560cc..7112fe84 100644 --- a/front/src/Phaser/Game/OutlineableInterface.ts +++ b/front/src/Phaser/Game/OutlineableInterface.ts @@ -3,8 +3,8 @@ export interface OutlineableInterface { removeFollowOutlineColor(): void; setApiOutlineColor(color: number): void; removeApiOutlineColor(): void; - pointerOverOutline(): void; + pointerOverOutline(color: number): void; pointerOutOutline(): void; - characterCloseByOutline(): void; + characterCloseByOutline(color: number): void; characterFarAwayOutline(): void; } diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts index 5ae00a94..3ee2ec2d 100644 --- a/front/src/Phaser/Game/StartPositionCalculator.ts +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -16,32 +16,6 @@ export class StartPositionCalculator { ) { this.initStartXAndStartY(); } - private initStartXAndStartY() { - // If there is an init position passed - if (this.initPosition !== null) { - this.startPosition = this.initPosition; - } else { - // Now, let's find the start layer - if (this.startLayerName) { - this.initPositionFromLayerName(this.startLayerName, this.startLayerName); - } - if (this.startPosition === undefined) { - // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. - this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); - } - } - // Still no start position? Something is wrong with the map, we need a "start" layer. - if (this.startPosition === undefined) { - console.warn( - 'This map is missing a layer named "start" that contains the available default start positions.' - ); - // Let's start in the middle of the map - this.startPosition = { - x: this.mapFile.width * 16, - y: this.mapFile.height * 16, - }; - } - } /** * @@ -76,6 +50,47 @@ export class StartPositionCalculator { } } + public getStartPositionNames(): string[] { + const names: string[] = []; + for (const layer of this.gameMap.flatLayers) { + if (layer.name === "start") { + names.push(layer.name); + continue; + } + if (this.isStartLayer(layer)) { + names.push(layer.name); + } + } + return names; + } + + private initStartXAndStartY() { + // If there is an init position passed + if (this.initPosition !== null) { + this.startPosition = this.initPosition; + } else { + // Now, let's find the start layer + if (this.startLayerName) { + this.initPositionFromLayerName(this.startLayerName, this.startLayerName); + } + if (this.startPosition === undefined) { + // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. + this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); + } + } + // Still no start position? Something is wrong with the map, we need a "start" layer. + if (this.startPosition === undefined) { + console.warn( + 'This map is missing a layer named "start" that contains the available default start positions.' + ); + // Let's start in the middle of the map + this.startPosition = { + x: this.mapFile.width * 16, + y: this.mapFile.height * 16, + }; + } + } + private isStartLayer(layer: ITiledMapLayer): boolean { return this.getProperty(layer, GameMapProperties.START_LAYER) == true; } diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts index 74810df4..d962648c 100644 --- a/front/src/Phaser/Map/ITiledMap.ts +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -78,6 +78,8 @@ export interface ITiledMapTileLayer { width: number; x: number; y: number; + parallaxx?: number; + parallaxy?: number; /** * Draw order (topdown (default), index) diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index a6fe29a1..514aa7d5 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -25,7 +25,7 @@ export class Player extends Character { direction: PlayerAnimationDirections, moving: boolean, companion: string | null, - companionTexturePromise?: Promise + companionTexturePromise?: CancelablePromise ) { super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise); diff --git a/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts b/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts index 4d9ac8a9..fc9e83cf 100644 --- a/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts +++ b/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts @@ -53,10 +53,20 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface { public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {} public handleSpaceKeyUpEvent(event: Event): Event { - const activatable = this.gameScene.getActivatablesManager().getSelectedActivatableObject(); - if (activatable && activatable.isActivatable()) { + const activatableManager = this.gameScene.getActivatablesManager(); + const activatable = activatableManager.getSelectedActivatableObject(); + if (activatable && activatable.isActivatable() && activatableManager.isSelectingByDistanceEnabled()) { activatable.activate(); } return event; } + + public addSpaceEventListener(callback: Function): void { + this.gameScene.input.keyboard.addListener("keyup-SPACE", callback); + this.gameScene.getActivatablesManager().disableSelectingByDistance(); + } + public removeSpaceEventListner(callback: Function): void { + this.gameScene.input.keyboard.removeListener("keyup-SPACE", callback); + this.gameScene.getActivatablesManager().enableSelectingByDistance(); + } } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index ffa67c3a..e7f814b9 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -223,10 +223,10 @@ export class UserInputManager { } addSpaceEventListner(callback: Function) { - this.scene.input.keyboard.addListener("keyup-SPACE", callback); + this.userInputHandler.addSpaceEventListener(callback); } removeSpaceEventListner(callback: Function) { - this.scene.input.keyboard.removeListener("keyup-SPACE", callback); + this.userInputHandler.removeSpaceEventListner(callback); } destroy(): void { @@ -255,6 +255,11 @@ export class UserInputManager { (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => { this.joystick?.hide(); this.userInputHandler.handlePointerUpEvent(pointer, gameObjects); + + // Disable focus on iframe (need by Firefox) + if (pointer.downElement.nodeName === "CANVAS" && document.activeElement instanceof HTMLIFrameElement) { + document.activeElement.blur(); + } } ); diff --git a/front/src/Stores/CoWebsiteStore.ts b/front/src/Stores/CoWebsiteStore.ts index 4227c405..c4ed2f65 100644 --- a/front/src/Stores/CoWebsiteStore.ts +++ b/front/src/Stores/CoWebsiteStore.ts @@ -1,5 +1,5 @@ -import { derived, get, writable } from "svelte/store"; -import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; +import { derived, writable } from "svelte/store"; +import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite"; function createCoWebsiteStore() { const { subscribe, set, update } = writable(Array()); @@ -9,7 +9,7 @@ function createCoWebsiteStore() { return { subscribe, add: (coWebsite: CoWebsite, position?: number) => { - coWebsite.state.subscribe((value) => { + coWebsite.getStateSubscriber().subscribe((value) => { update((currentArray) => currentArray); }); @@ -31,7 +31,7 @@ function createCoWebsiteStore() { }, remove: (coWebsite: CoWebsite) => { update((currentArray) => [ - ...currentArray.filter((currentCoWebsite) => currentCoWebsite.iframe.id !== coWebsite.iframe.id), + ...currentArray.filter((currentCoWebsite) => currentCoWebsite.getId() !== coWebsite.getId()), ]); }, empty: () => { @@ -43,9 +43,9 @@ function createCoWebsiteStore() { export const coWebsites = createCoWebsiteStore(); export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) => - $coWebsites.filter((coWebsite) => get(coWebsite.state) !== "asleep") + $coWebsites.filter((coWebsite) => coWebsite.getState() !== "asleep") ); export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) => - $coWebsites.find((coWebsite) => get(coWebsite.state) !== "asleep") + $coWebsites.find((coWebsite) => coWebsite.getState() !== "asleep") ); diff --git a/front/src/Stores/EmbedScreensStore.ts b/front/src/Stores/EmbedScreensStore.ts index 0db7c675..724733b3 100644 --- a/front/src/Stores/EmbedScreensStore.ts +++ b/front/src/Stores/EmbedScreensStore.ts @@ -1,5 +1,5 @@ import { derived, get, writable } from "svelte/store"; -import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; +import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite"; import { LayoutMode } from "../WebRtc/LayoutManager"; import { coWebsites } from "./CoWebsiteStore"; import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore"; @@ -31,7 +31,7 @@ function createHighlightedEmbedScreenStore() { embedScreen.type !== currentEmbedScreen.type || (embedScreen.type === "cowebsite" && currentEmbedScreen.type === "cowebsite" && - embedScreen.embed.iframe.id !== currentEmbedScreen.embed.iframe.id) || + embedScreen.embed.getId() !== currentEmbedScreen.embed.getId()) || (embedScreen.type === "streamable" && currentEmbedScreen.type === "streamable" && embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId) diff --git a/front/src/Stores/LayoutManagerStore.ts b/front/src/Stores/LayoutManagerStore.ts index b6f428aa..e0f8d955 100644 --- a/front/src/Stores/LayoutManagerStore.ts +++ b/front/src/Stores/LayoutManagerStore.ts @@ -1,4 +1,5 @@ import { derived, writable } from "svelte/store"; +import type { ActivatablesManager } from "../Phaser/Game/ActivatablesManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; export interface LayoutManagerAction { diff --git a/front/src/Stores/OutlineColorStore.ts b/front/src/Stores/OutlineColorStore.ts index a35cc9c9..8ecd7293 100644 --- a/front/src/Stores/OutlineColorStore.ts +++ b/front/src/Stores/OutlineColorStore.ts @@ -5,38 +5,33 @@ export function createColorStore() { let followColor: number | undefined = undefined; let apiColor: number | undefined = undefined; - - let pointedByPointer: boolean = false; - let pointedByCharacter: boolean = false; + let pointedByPointer: number | undefined = undefined; + let pointedByCharacter: number | undefined = undefined; const updateColor = () => { - if (pointedByPointer || pointedByCharacter) { - set(0xffff00); - } else { - set(followColor ?? apiColor); - } + set(pointedByPointer ?? pointedByCharacter ?? followColor ?? apiColor); }; return { subscribe, - pointerOver() { - pointedByPointer = true; + pointerOver(color: number) { + pointedByPointer = color; updateColor(); }, pointerOut() { - pointedByPointer = false; + pointedByPointer = undefined; updateColor(); }, - characterCloseBy() { - pointedByCharacter = true; + characterCloseBy(color: number) { + pointedByCharacter = color; updateColor(); }, characterFarAway() { - pointedByCharacter = false; + pointedByCharacter = undefined; updateColor(); }, diff --git a/front/src/Stores/StartLayerNamesStore.ts b/front/src/Stores/StartLayerNamesStore.ts new file mode 100644 index 00000000..a6397872 --- /dev/null +++ b/front/src/Stores/StartLayerNamesStore.ts @@ -0,0 +1,6 @@ +import { Readable, writable } from "svelte/store"; + +/** + * A store that contains the map starting layers names + */ +export const startLayerNamesStore = writable([]); diff --git a/front/src/Utils/PathfindingManager.ts b/front/src/Utils/PathfindingManager.ts index 71205070..c5057ed8 100644 --- a/front/src/Utils/PathfindingManager.ts +++ b/front/src/Utils/PathfindingManager.ts @@ -19,6 +19,10 @@ export class PathfindingManager { this.setEasyStarGrid(collisionsGrid); } + public setCollisionGrid(collisionGrid: number[][]): void { + this.setEasyStarGrid(collisionGrid); + } + public async findPath( start: { x: number; y: number }, end: { x: number; y: number }, diff --git a/front/src/Utils/StringUtils.ts b/front/src/Utils/StringUtils.ts new file mode 100644 index 00000000..1f168c15 --- /dev/null +++ b/front/src/Utils/StringUtils.ts @@ -0,0 +1,12 @@ +export class StringUtils { + public static parsePointFromParam(param: string, separator: string = ","): { x: number; y: number } | undefined { + const values = param.split(separator).map((val) => parseInt(val)); + if (values.length !== 2) { + return; + } + if (isNaN(values[0]) || isNaN(values[1])) { + return; + } + return { x: values[0], y: values[1] }; + } +} diff --git a/front/src/WebRtc/CoWebsite/CoWesbite.ts b/front/src/WebRtc/CoWebsite/CoWesbite.ts new file mode 100644 index 00000000..50ce3b9f --- /dev/null +++ b/front/src/WebRtc/CoWebsite/CoWesbite.ts @@ -0,0 +1,17 @@ +import type CancelablePromise from "cancelable-promise"; +import type { Readable, Writable } from "svelte/store"; + +export type CoWebsiteState = "asleep" | "loading" | "ready"; + +export interface CoWebsite { + getId(): string; + getUrl(): URL; + getState(): CoWebsiteState; + getStateSubscriber(): Readable; + getIframe(): HTMLIFrameElement | undefined; + getLoadIframe(): CancelablePromise | undefined; + getWidthPercent(): number | undefined; + isClosable(): boolean; + load(): CancelablePromise; + unload(): Promise; +} diff --git a/front/src/WebRtc/CoWebsite/JitsiCoWebsite.ts b/front/src/WebRtc/CoWebsite/JitsiCoWebsite.ts new file mode 100644 index 00000000..a09b2bcb --- /dev/null +++ b/front/src/WebRtc/CoWebsite/JitsiCoWebsite.ts @@ -0,0 +1,49 @@ +import CancelablePromise from "cancelable-promise"; +import { gameManager } from "../../Phaser/Game/GameManager"; +import { jitsiFactory } from "../JitsiFactory"; +import { SimpleCoWebsite } from "./SimpleCoWebsite"; + +export class JitsiCoWebsite extends SimpleCoWebsite { + private jitsiLoadPromise?: () => CancelablePromise; + + setJitsiLoadPromise(promise: () => CancelablePromise): void { + this.jitsiLoadPromise = promise; + } + + load(): CancelablePromise { + return new CancelablePromise((resolve, reject, cancel) => { + this.state.set("loading"); + + gameManager.getCurrentGameScene().disableMediaBehaviors(); + + if (!this.jitsiLoadPromise) { + return reject("Undefined Jitsi start callback"); + } + + const jitsiLoading = this.jitsiLoadPromise() + .then((iframe) => { + this.iframe = iframe; + this.iframe.classList.add("pixel"); + this.state.set("ready"); + return resolve(iframe); + }) + .catch((err) => { + return reject(err); + }); + + cancel(() => { + jitsiLoading.cancel(); + this.unload().catch((err) => { + console.error("Cannot unload Jitsi co-website while cancel loading", err); + }); + }); + }); + } + + unload(): Promise { + jitsiFactory.destroy(); + gameManager.getCurrentGameScene().enableMediaBehaviors(); + + return super.unload(); + } +} diff --git a/front/src/WebRtc/CoWebsite/SimpleCoWebsite.ts b/front/src/WebRtc/CoWebsite/SimpleCoWebsite.ts new file mode 100644 index 00000000..1ad585b4 --- /dev/null +++ b/front/src/WebRtc/CoWebsite/SimpleCoWebsite.ts @@ -0,0 +1,133 @@ +import CancelablePromise from "cancelable-promise"; +import { get, Readable, writable, Writable } from "svelte/store"; +import { iframeListener } from "../../Api/IframeListener"; +import { coWebsiteManager } from "../CoWebsiteManager"; +import type { CoWebsite, CoWebsiteState } from "./CoWesbite"; + +export class SimpleCoWebsite implements CoWebsite { + protected id: string; + protected url: URL; + protected state: Writable; + protected iframe?: HTMLIFrameElement; + protected loadIframe?: CancelablePromise; + protected allowApi?: boolean; + protected allowPolicy?: string; + protected widthPercent?: number; + protected closable: boolean; + + constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean) { + this.id = coWebsiteManager.generateUniqueId(); + this.url = url; + this.state = writable("asleep" as CoWebsiteState); + this.allowApi = allowApi; + this.allowPolicy = allowPolicy; + this.widthPercent = widthPercent; + this.closable = closable ?? false; + } + + getId(): string { + return this.id; + } + + getUrl(): URL { + return this.url; + } + + getState(): CoWebsiteState { + return get(this.state); + } + + getStateSubscriber(): Readable { + return this.state; + } + + getIframe(): HTMLIFrameElement | undefined { + return this.iframe; + } + + getLoadIframe(): CancelablePromise | undefined { + return this.loadIframe; + } + + getWidthPercent(): number | undefined { + return this.widthPercent; + } + + isClosable(): boolean { + return this.closable; + } + + load(): CancelablePromise { + this.loadIframe = new CancelablePromise((resolve, reject, cancel) => { + this.state.set("loading"); + + const iframe = document.createElement("iframe"); + this.iframe = iframe; + this.iframe.src = this.url.toString(); + this.iframe.id = this.id; + + if (this.allowPolicy) { + this.iframe.allow = this.allowPolicy; + } + + if (this.allowApi) { + iframeListener.registerIframe(this.iframe); + } + + this.iframe.classList.add("pixel"); + + const onloadPromise = new Promise((resolve) => { + if (this.iframe) { + this.iframe.onload = () => { + this.state.set("ready"); + resolve(); + }; + } + }); + + const onTimeoutPromise = new Promise((resolve) => { + setTimeout(() => resolve(), 2000); + }); + + coWebsiteManager.getCoWebsiteBuffer().appendChild(this.iframe); + + const race = CancelablePromise.race([onloadPromise, onTimeoutPromise]) + .then(() => { + return resolve(iframe); + }) + .catch((err) => { + console.error("Error on co-website loading => ", err); + return reject(); + }); + + cancel(() => { + race.cancel(); + this.unload().catch((err) => { + console.error("Cannot unload co-website while cancel loading", err); + }); + }); + }); + + return this.loadIframe; + } + + unload(): Promise { + return new Promise((resolve) => { + if (this.iframe) { + if (this.allowApi) { + iframeListener.unregisterIframe(this.iframe); + } + this.iframe.parentNode?.removeChild(this.iframe); + } + + if (this.loadIframe) { + this.loadIframe.cancel(); + this.loadIframe = undefined; + } + + this.state.set("asleep"); + + resolve(); + }); + } +} diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 8e646d9d..a5c57ed6 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -1,16 +1,15 @@ import { HtmlUtils } from "./HtmlUtils"; import { Subject } from "rxjs"; -import { iframeListener } from "../Api/IframeListener"; import { waScaleManager } from "../Phaser/Services/WaScaleManager"; import { coWebsites, coWebsitesNotAsleep, mainCoWebsite } from "../Stores/CoWebsiteStore"; -import { get, Writable, writable } from "svelte/store"; +import { get, Readable, Writable, writable } from "svelte/store"; import { embedScreenLayout, highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; import { isMediaBreakpointDown } from "../Utils/BreakpointsUtils"; -import { jitsiFactory } from "./JitsiFactory"; -import { gameManager } from "../Phaser/Game/GameManager"; import { LayoutMode } from "./LayoutManager"; +import type { CoWebsite } from "./CoWebsite/CoWesbite"; +import type CancelablePromise from "cancelable-promise"; -enum iframeStates { +export enum iframeStates { closed = 1, loading, // loading an iframe can be slow, so we show some placeholder until it is ready opened, @@ -21,7 +20,7 @@ const gameOverlayDomId = "game-overlay"; const cowebsiteBufferDomId = "cowebsite-buffer"; // the id of the container who contains cowebsite iframes. const cowebsiteAsideHolderDomId = "cowebsite-aside-holder"; const cowebsiteLoaderDomId = "cowebsite-loader"; -export const cowebsiteCloseButtonId = "cowebsite-close"; +const cowebsiteCloseButtonId = "cowebsite-close"; const cowebsiteFullScreenButtonId = "cowebsite-fullscreen"; const cowebsiteOpenFullScreenImageId = "cowebsite-fullscreen-open"; const cowebsiteCloseFullScreenImageId = "cowebsite-fullscreen-close"; @@ -34,29 +33,12 @@ interface TouchMoveCoordinates { y: number; } -export type CoWebsiteState = "asleep" | "loading" | "ready"; - -export type CoWebsite = { - iframe: HTMLIFrameElement; - url: URL; - state: Writable; - closable: boolean; - allowPolicy: string | undefined; - allowApi: boolean | undefined; - jitsi?: boolean; - altMessage?: string; -}; - class CoWebsiteManager { - private openedMain: iframeStates = iframeStates.closed; + private openedMain: Writable = writable(iframeStates.closed); private _onResize: Subject = new Subject(); public onResize = this._onResize.asObservable(); - /** - * Quickly going in and out of an iframe trigger can create conflicts between the iframe states. - * So we use this promise to queue up every cowebsite state transition - */ - private currentOperationPromise: Promise = Promise.resolve(); + private cowebsiteDom: HTMLDivElement; private resizing: boolean = false; private gameOverlayDom: HTMLDivElement; @@ -74,6 +56,14 @@ class CoWebsiteManager { this.resizeAllIframes(); }); + public getMainState() { + return get(this.openedMain); + } + + public getMainStateSubscriber(): Readable { + return this.openedMain; + } + get width(): number { return this.cowebsiteDom.clientWidth; } @@ -82,8 +72,13 @@ class CoWebsiteManager { this.cowebsiteDom.style.width = width + "px"; } - set widthPercent(width: number) { - this.cowebsiteDom.style.width = width + "%"; + get maxWidth(): number { + let maxWidth = 75 * window.innerWidth; + if (maxWidth !== 0) { + maxWidth = Math.round(maxWidth / 100); + } + + return maxWidth; } get height(): number { @@ -94,6 +89,15 @@ class CoWebsiteManager { this.cowebsiteDom.style.height = height + "px"; } + get maxHeight(): number { + let maxHeight = 60 * window.innerHeight; + if (maxHeight !== 0) { + maxHeight = Math.round(maxHeight / 100); + } + + return maxHeight; + } + get verticalMode(): boolean { return window.innerWidth < window.innerHeight; } @@ -128,13 +132,11 @@ class CoWebsiteManager { throw new Error("Undefined main co-website on closing"); } - if (coWebsite.closable) { - this.closeCoWebsite(coWebsite).catch(() => { - console.error("Error during closing a co-website by a button"); - }); + if (coWebsite.isClosable()) { + this.closeCoWebsite(coWebsite); } else { - this.unloadCoWebsite(coWebsite).catch(() => { - console.error("Error during unloading a co-website by a button"); + this.unloadCoWebsite(coWebsite).catch((err) => { + console.error("Cannot unload co-website on click on close button", err); }); } }); @@ -191,29 +193,21 @@ class CoWebsiteManager { if (this.verticalMode) { const tempValue = this.height + y; - let maxHeight = 60 * window.innerHeight; - if (maxHeight !== 0) { - maxHeight = Math.round(maxHeight / 100); - } if (tempValue < this.cowebsiteAsideHolderDom.offsetHeight) { this.height = this.cowebsiteAsideHolderDom.offsetHeight; - } else if (tempValue > maxHeight) { - this.height = maxHeight; + } else if (tempValue > this.maxHeight) { + this.height = this.maxHeight; } else { this.height = tempValue; } } else { const tempValue = this.width - x; - let maxWidth = 75 * window.innerWidth; - if (maxWidth !== 0) { - maxWidth = Math.round(maxWidth / 100); - } if (tempValue < this.cowebsiteAsideHolderDom.offsetWidth) { this.width = this.cowebsiteAsideHolderDom.offsetWidth; - } else if (tempValue > maxWidth) { - this.width = maxWidth; + } else if (tempValue > this.maxWidth) { + this.width = this.maxWidth; } else { this.width = tempValue; } @@ -230,7 +224,10 @@ class CoWebsiteManager { return; } - coWebsite.iframe.style.display = "none"; + const iframe = coWebsite.getIframe(); + if (iframe) { + iframe.style.display = "none"; + } this.resizing = true; document.addEventListener("mousemove", movecallback); }); @@ -246,7 +243,10 @@ class CoWebsiteManager { return; } - coWebsite.iframe.style.display = "flex"; + const iframe = coWebsite.getIframe(); + if (iframe) { + iframe.style.display = "flex"; + } this.resizing = false; }); @@ -259,7 +259,10 @@ class CoWebsiteManager { return; } - coWebsite.iframe.style.display = "none"; + const iframe = coWebsite.getIframe(); + if (iframe) { + iframe.style.display = "none"; + } this.resizing = true; const touchEvent = event.touches[0]; this.previousTouchMoveCoordinates = { x: touchEvent.pageX, y: touchEvent.pageY }; @@ -278,7 +281,10 @@ class CoWebsiteManager { return; } - coWebsite.iframe.style.display = "flex"; + const iframe = coWebsite.getIframe(); + if (iframe) { + iframe.style.display = "flex"; + } this.resizing = false; }); } @@ -299,16 +305,43 @@ class CoWebsiteManager { }); } + public displayMain() { + const coWebsite = this.getMainCoWebsite(); + if (coWebsite) { + const iframe = coWebsite.getIframe(); + if (iframe) { + iframe.style.display = "block"; + } + } + this.loadMain(coWebsite?.getWidthPercent()); + this.openMain(); + this.fire(); + } + + public hideMain() { + const coWebsite = this.getMainCoWebsite(); + if (coWebsite) { + const iframe = coWebsite.getIframe(); + if (iframe) { + iframe.style.display = "none"; + } + } + this.cowebsiteDom.classList.add("closing"); + this.cowebsiteDom.classList.remove("opened"); + this.openedMain.set(iframeStates.closed); + this.fire(); + } + private closeMain(): void { this.toggleFullScreenIcon(true); this.cowebsiteDom.classList.add("closing"); this.cowebsiteDom.classList.remove("opened"); - this.openedMain = iframeStates.closed; + this.openedMain.set(iframeStates.closed); this.resetStyleMain(); this.fire(); } - private loadMain(): void { + private loadMain(openingWidth?: number): void { this.loaderAnimationInterval.interval = setInterval(() => { if (!this.loaderAnimationInterval.trails) { this.loaderAnimationInterval.trails = [0, 1, 2]; @@ -337,16 +370,34 @@ class CoWebsiteManager { trail === 3 ? 0 : trail + 1 ); }, 200); + + if (!this.verticalMode && openingWidth) { + let newWidth = 50; + + if (openingWidth > 100) { + newWidth = 100; + } else if (openingWidth > 1) { + newWidth = openingWidth; + } + + newWidth = Math.round((newWidth * this.maxWidth) / 100); + + if (newWidth < this.cowebsiteAsideHolderDom.offsetWidth) { + newWidth = this.cowebsiteAsideHolderDom.offsetWidth; + } + + this.width = newWidth; + } + this.cowebsiteDom.classList.add("opened"); - this.openedMain = iframeStates.loading; + this.openedMain.set(iframeStates.loading); } private openMain(): void { this.cowebsiteDom.addEventListener("transitionend", () => { this.resizeAllIframes(); }); - this.openedMain = iframeStates.opened; - this.resetStyleMain(); + this.openedMain.set(iframeStates.opened); } public resetStyleMain() { @@ -359,7 +410,9 @@ class CoWebsiteManager { } public getCoWebsiteById(coWebsiteId: string): CoWebsite | undefined { - return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId); + return get(coWebsites).find((coWebsite: CoWebsite) => { + return coWebsite.getId() === coWebsiteId; + }); } private getCoWebsiteByPosition(position: number): CoWebsite | undefined { @@ -379,7 +432,9 @@ class CoWebsiteManager { } private getPositionByCoWebsite(coWebsite: CoWebsite): number { - return get(coWebsites).findIndex((currentCoWebsite) => currentCoWebsite.iframe.id === coWebsite.iframe.id); + return get(coWebsites).findIndex((currentCoWebsite) => { + return currentCoWebsite.getId() === coWebsite.getId(); + }); } private getSlotByCowebsite(coWebsite: CoWebsite): HTMLDivElement | undefined { @@ -393,7 +448,7 @@ class CoWebsiteManager { if (index === 0) { id += "main"; } else { - id += coWebsite.iframe.id; + id += coWebsite.getId(); } const slot = HtmlUtils.getElementById(id); @@ -410,60 +465,72 @@ class CoWebsiteManager { const bounding = coWebsiteSlot.getBoundingClientRect(); - coWebsite.iframe.style.top = bounding.top + "px"; - coWebsite.iframe.style.left = bounding.left + "px"; - coWebsite.iframe.style.width = bounding.right - bounding.left + "px"; - coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px"; + const iframe = coWebsite.getIframe(); + + if (iframe) { + iframe.style.top = bounding.top + "px"; + iframe.style.left = bounding.left + "px"; + iframe.style.width = bounding.right - bounding.left + "px"; + iframe.style.height = bounding.bottom - bounding.top + "px"; + } } public resizeAllIframes() { const mainCoWebsite = this.getCoWebsiteByPosition(0); + const mainIframe = mainCoWebsite?.getIframe(); const highlightEmbed = get(highlightedEmbedScreen); - get(coWebsites).forEach((coWebsite) => { - const notMain = !mainCoWebsite || (mainCoWebsite && mainCoWebsite.iframe.id !== coWebsite.iframe.id); + get(coWebsites).forEach((coWebsite: CoWebsite) => { + const iframe = coWebsite.getIframe(); + if (!iframe) { + return; + } + + const notMain = !mainCoWebsite || (mainCoWebsite && mainIframe && mainIframe.id !== iframe.id); const notHighlighEmbed = !highlightEmbed || (highlightEmbed && (highlightEmbed.type !== "cowebsite" || - (highlightEmbed.type === "cowebsite" && - highlightEmbed.embed.iframe.id !== coWebsite.iframe.id))); + (highlightEmbed.type === "cowebsite" && highlightEmbed.embed.getId() !== coWebsite.getId()))); - if (coWebsite.iframe.classList.contains("main") && notMain) { - coWebsite.iframe.classList.remove("main"); + if (iframe.classList.contains("main") && notMain) { + iframe.classList.remove("main"); } - if (coWebsite.iframe.classList.contains("highlighted") && notHighlighEmbed) { - coWebsite.iframe.classList.remove("highlighted"); - coWebsite.iframe.classList.add("pixel"); - coWebsite.iframe.style.top = "-1px"; - coWebsite.iframe.style.left = "-1px"; + if (iframe.classList.contains("highlighted") && notHighlighEmbed) { + iframe.classList.remove("highlighted"); + iframe.classList.add("pixel"); + iframe.style.top = "-1px"; + iframe.style.left = "-1px"; } if (notMain && notHighlighEmbed) { - coWebsite.iframe.classList.add("pixel"); - coWebsite.iframe.style.top = "-1px"; - coWebsite.iframe.style.left = "-1px"; + iframe.classList.add("pixel"); + iframe.style.top = "-1px"; + iframe.style.left = "-1px"; } this.setIframeOffset(coWebsite); }); - if (mainCoWebsite) { - mainCoWebsite.iframe.classList.add("main"); - mainCoWebsite.iframe.classList.remove("pixel"); + if (mainIframe) { + mainIframe.classList.add("main"); + mainIframe.classList.remove("pixel"); } if (highlightEmbed && highlightEmbed.type === "cowebsite") { - highlightEmbed.embed.iframe.classList.add("highlighted"); - highlightEmbed.embed.iframe.classList.remove("pixel"); + const highlightEmbedIframe = highlightEmbed.embed.getIframe(); + if (highlightEmbedIframe) { + highlightEmbedIframe.classList.add("highlighted"); + highlightEmbedIframe.classList.remove("pixel"); + } } } private removeHighlightCoWebsite(coWebsite: CoWebsite) { const highlighted = get(highlightedEmbedScreen); - if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.iframe.id === coWebsite.iframe.id) { + if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.getId() === coWebsite.getId()) { highlightedEmbedScreen.removeHighlight(); } } @@ -476,7 +543,9 @@ class CoWebsiteManager { this.closeMain(); } - coWebsite.iframe.remove(); + coWebsite.unload().catch((err) => { + console.error("Cannot unload cowebsite on remove from stack"); + }); } public goToMain(coWebsite: CoWebsite) { @@ -488,38 +557,21 @@ class CoWebsiteManager { isMediaBreakpointDown("lg") && get(embedScreenLayout) === LayoutMode.Presentation && mainCoWebsite && - mainCoWebsite.iframe.id !== coWebsite.iframe.id && - get(mainCoWebsite.state) !== "asleep" + mainCoWebsite.getId() !== coWebsite.getId() && + mainCoWebsite.getState() !== "asleep" ) { - highlightedEmbedScreen.toggleHighlight({ - type: "cowebsite", - embed: mainCoWebsite, - }); + highlightedEmbedScreen.removeHighlight(); } this.resizeAllIframes(); } - public searchJitsi(): CoWebsite | undefined { - return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.jitsi); - } - - private initialiseCowebsite(coWebsite: CoWebsite, position: number | undefined) { - if (coWebsite.allowPolicy) { - coWebsite.iframe.allow = coWebsite.allowPolicy; - } - - if (coWebsite.allowApi) { - iframeListener.registerIframe(coWebsite.iframe); - } - - coWebsite.iframe.classList.add("pixel"); - + public addCoWebsiteToStore(coWebsite: CoWebsite, position: number | undefined) { const coWebsitePosition = position === undefined ? get(coWebsites).length : position; coWebsites.add(coWebsite, coWebsitePosition); } - private generateUniqueId() { + public generateUniqueId() { let id = undefined; do { id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); @@ -528,201 +580,89 @@ class CoWebsiteManager { return id; } - public addCoWebsite( - url: string, - base: string, - allowApi?: boolean, - allowPolicy?: string, - position?: number, - closable?: boolean, - altMessage?: string - ): CoWebsite { - const iframe = document.createElement("iframe"); - const fullUrl = new URL(url, base); - iframe.src = fullUrl.toString(); - iframe.id = this.generateUniqueId(); - - const newCoWebsite: CoWebsite = { - iframe, - url: fullUrl, - state: writable("asleep" as CoWebsiteState), - closable: closable ?? false, - allowPolicy, - allowApi, - altMessage, - }; - - this.initialiseCowebsite(newCoWebsite, position); - - return newCoWebsite; - } - - public addCoWebsiteFromIframe( - iframe: HTMLIFrameElement, - allowApi?: boolean, - allowPolicy?: string, - position?: number, - closable?: boolean, - jitsi?: boolean - ): CoWebsite { - if (get(coWebsitesNotAsleep).length < 1) { - this.loadMain(); - } - - iframe.id = this.generateUniqueId(); - - const newCoWebsite: CoWebsite = { - iframe, - url: new URL(iframe.src), - state: writable("ready" as CoWebsiteState), - closable: closable ?? false, - allowPolicy, - allowApi, - jitsi, - }; - - if (position === 0) { - this.openMain(); - setTimeout(() => { - this.fire(); - }, animationTime); - } - - this.initialiseCowebsite(newCoWebsite, position); - - return newCoWebsite; - } - - public loadCoWebsite(coWebsite: CoWebsite): Promise { + public loadCoWebsite(coWebsite: CoWebsite): CancelablePromise { if (get(coWebsitesNotAsleep).length < 1) { coWebsites.remove(coWebsite); coWebsites.add(coWebsite, 0); - this.loadMain(); + this.loadMain(coWebsite.getWidthPercent()); } - coWebsite.state.set("loading"); + // Check if the main is hide + if (this.getMainCoWebsite() && this.getMainState() === iframeStates.closed) { + this.displayMain(); + } - const mainCoWebsite = this.getMainCoWebsite(); + const coWebsiteLloading = coWebsite + .load() + .then(() => { + const mainCoWebsite = this.getMainCoWebsite(); + if (mainCoWebsite && mainCoWebsite.getId() === coWebsite.getId()) { + this.openMain(); - return new Promise((resolve, reject) => { - const onloadPromise = new Promise((resolve) => { - coWebsite.iframe.onload = () => { - coWebsite.state.set("ready"); - resolve(); - }; + setTimeout(() => { + this.fire(); + }, animationTime); + } + this.resizeAllIframes(); + }) + .catch((err) => { + console.error("Error on co-website loading => ", err); + this.removeCoWebsiteFromStack(coWebsite); }); - const onTimeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 2000); - }); - - this.cowebsiteBufferDom.appendChild(coWebsite.iframe); - - if (coWebsite.jitsi) { - const gameScene = gameManager.getCurrentGameScene(); - gameScene.disableMediaBehaviors(); - jitsiFactory.restart(); - } - - this.currentOperationPromise = this.currentOperationPromise - .then(() => Promise.race([onloadPromise, onTimeoutPromise])) - .then(() => { - if (mainCoWebsite && mainCoWebsite.iframe.id === coWebsite.iframe.id) { - this.openMain(); - - setTimeout(() => { - this.fire(); - }, animationTime); - } - - return resolve(coWebsite); - }) - .catch((err) => { - console.error("Error on co-website loading => ", err); - this.removeCoWebsiteFromStack(coWebsite); - return reject(); - }); - }); + return coWebsiteLloading; } public unloadCoWebsite(coWebsite: CoWebsite): Promise { - return new Promise((resolve, reject) => { - this.removeHighlightCoWebsite(coWebsite); + this.removeHighlightCoWebsite(coWebsite); - coWebsite.iframe.parentNode?.removeChild(coWebsite.iframe); - coWebsite.state.set("asleep"); - coWebsites.remove(coWebsite); + return coWebsite + .unload() + .then(() => { + coWebsites.remove(coWebsite); + const mainCoWebsite = this.getMainCoWebsite(); - if (coWebsite.jitsi) { - jitsiFactory.stop(); - const gameScene = gameManager.getCurrentGameScene(); - gameScene.enableMediaBehaviors(); - } + if (mainCoWebsite) { + this.removeHighlightCoWebsite(mainCoWebsite); + this.goToMain(mainCoWebsite); + this.resizeAllIframes(); + } else { + this.closeMain(); + } - const mainCoWebsite = this.getMainCoWebsite(); + coWebsites.add(coWebsite, get(coWebsites).length); + }) + .catch(() => { + console.error(); + }); + } - if (mainCoWebsite) { - this.removeHighlightCoWebsite(mainCoWebsite); - this.goToMain(mainCoWebsite); - this.resizeAllIframes(); - } else { - this.closeMain(); - } + public closeCoWebsite(coWebsite: CoWebsite): void { + if (get(coWebsites).length === 1) { + this.fire(); + } - coWebsites.add(coWebsite, get(coWebsites).length); + this.removeCoWebsiteFromStack(coWebsite); - resolve(); + const mainCoWebsite = this.getMainCoWebsite(); + + if (mainCoWebsite) { + this.removeHighlightCoWebsite(mainCoWebsite); + this.goToMain(mainCoWebsite); + this.resizeAllIframes(); + } else { + this.closeMain(); + } + } + + public closeCoWebsites(): void { + get(coWebsites).forEach((coWebsite: CoWebsite) => { + this.closeCoWebsite(coWebsite); }); } - public closeCoWebsite(coWebsite: CoWebsite): Promise { - this.currentOperationPromise = this.currentOperationPromise.then( - () => - new Promise((resolve) => { - if (coWebsite.jitsi) { - jitsiFactory.destroy(); - const gameScene = gameManager.getCurrentGameScene(); - gameScene.enableMediaBehaviors(); - } - - if (get(coWebsites).length === 1) { - this.fire(); - } - - if (coWebsite.allowApi) { - iframeListener.unregisterIframe(coWebsite.iframe); - } - - this.removeCoWebsiteFromStack(coWebsite); - - const mainCoWebsite = this.getMainCoWebsite(); - - if (mainCoWebsite) { - this.removeHighlightCoWebsite(mainCoWebsite); - this.goToMain(mainCoWebsite); - this.resizeAllIframes(); - } else { - this.closeMain(); - } - resolve(); - }) - ); - return this.currentOperationPromise; - } - - public closeCoWebsites(): Promise { - return (this.currentOperationPromise = this.currentOperationPromise.then(() => { - get(coWebsites).forEach((coWebsite: CoWebsite) => { - this.closeCoWebsite(coWebsite).catch(() => { - console.error("Error during closing a co-website"); - }); - }); - })); - return this.currentOperationPromise; - } - public getGameSize(): { width: number; height: number } { - if (this.openedMain === iframeStates.closed) { + if (this.getMainState() === iframeStates.closed) { return { width: window.innerWidth, height: window.innerHeight, diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index b273a64c..d328a72d 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -1,7 +1,8 @@ import { JITSI_URL } from "../Enum/EnvironmentVariable"; -import { CoWebsite, coWebsiteManager } from "./CoWebsiteManager"; +import { coWebsiteManager } from "./CoWebsiteManager"; import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore"; import { get } from "svelte/store"; +import CancelablePromise from "cancelable-promise"; interface jitsiConfigInterface { startWithAudioMuted: boolean; @@ -134,114 +135,96 @@ class JitsiFactory { return slugify(instance.replace("/", "-") + "-" + roomName); } - public async start( + public start( roomName: string, playerName: string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string - ) { - const coWebsite = coWebsiteManager.searchJitsi(); + ): CancelablePromise { + return new CancelablePromise((resolve, reject, cancel) => { + // Jitsi meet external API maintains some data in local storage + // which is sent via the appData URL parameter when joining a + // conference. Problem is that this data grows indefinitely. Thus + // after some time the URLs get so huge that loading the iframe + // becomes slow and eventually breaks completely. Thus lets just + // clear jitsi local storage before starting a new conference. + window.localStorage.removeItem("jitsiLocalStorage"); - if (coWebsite) { - await coWebsiteManager.closeCoWebsite(coWebsite); - } - - // Jitsi meet external API maintains some data in local storage - // which is sent via the appData URL parameter when joining a - // conference. Problem is that this data grows indefinitely. Thus - // after some time the URLs get so huge that loading the iframe - // becomes slow and eventually breaks completely. Thus lets just - // clear jitsi local storage before starting a new conference. - window.localStorage.removeItem("jitsiLocalStorage"); - - const domain = jitsiUrl || JITSI_URL; - if (domain === undefined) { - throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); - } - await this.loadJitsiScript(domain); - - const options: JitsiOptions = { - roomName: roomName, - jwt: jwt, - width: "100%", - height: "100%", - parentNode: coWebsiteManager.getCoWebsiteBuffer(), - configOverwrite: mergeConfig(config), - interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig }, - }; - - if (!options.jwt) { - delete options.jwt; - } - - const doResolve = (): void => { - const iframe = coWebsiteManager.getCoWebsiteBuffer().querySelector('[id*="jitsi" i]'); - if (iframe && this.jitsiApi) { - const coWebsite = coWebsiteManager.addCoWebsiteFromIframe(iframe, false, undefined, 0, false, true); - - this.jitsiApi.addListener("videoConferenceLeft", () => { - this.closeOrUnload(coWebsite); - }); - - this.jitsiApi.addListener("readyToClose", () => { - this.closeOrUnload(coWebsite); - }); + const domain = jitsiUrl || JITSI_URL; + if (domain === undefined) { + throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); } - coWebsiteManager.resizeAllIframes(); - }; + const loadScript = this.loadJitsiScript(domain).then(() => { + const options: JitsiOptions = { + roomName: roomName, + jwt: jwt, + width: "100%", + height: "100%", + parentNode: coWebsiteManager.getCoWebsiteBuffer(), + configOverwrite: mergeConfig(config), + interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig }, + }; - this.jitsiApi = undefined; + if (!options.jwt) { + delete options.jwt; + } - options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. - setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load - this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); - this.jitsiApi.executeCommand("displayName", playerName); + const timemout = setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load - this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); - this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); + const doResolve = (): void => { + clearTimeout(timemout); + const iframe = coWebsiteManager + .getCoWebsiteBuffer() + .querySelector('[id*="jitsi" i]'); + + if (iframe && this.jitsiApi) { + this.jitsiApi.addListener("videoConferenceLeft", () => { + this.closeOrUnload(iframe); + }); + + this.jitsiApi.addListener("readyToClose", () => { + this.closeOrUnload(iframe); + }); + + return resolve(iframe); + } + }; + + this.jitsiApi = undefined; + + options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. + this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); + this.jitsiApi.executeCommand("displayName", playerName); + + this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); + this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); + }); + + cancel(() => { + loadScript.cancel(); + }); + }); } - private closeOrUnload = function (coWebsite: CoWebsite) { - if (coWebsite.closable) { - coWebsiteManager.closeCoWebsite(coWebsite).catch(() => { - console.error("Error during closing a Jitsi Meet"); - }); + private closeOrUnload = function (iframe: HTMLIFrameElement) { + const coWebsite = coWebsiteManager.getCoWebsites().find((coWebsite) => coWebsite.getIframe() === iframe); + + if (!coWebsite) { + return; + } + + if (coWebsite.isClosable()) { + coWebsiteManager.closeCoWebsite(coWebsite); } else { - coWebsiteManager.unloadCoWebsite(coWebsite).catch(() => { - console.error("Error during unloading a Jitsi Meet"); + coWebsiteManager.unloadCoWebsite(coWebsite).catch((err) => { + console.error("Cannot unload co-website from the Jitsi factory", err); }); } }; - public restart() { - if (!this.jitsiApi) { - return; - } - - this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); - this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); - - const coWebsite = coWebsiteManager.searchJitsi(); - console.log("jitsi api ", this.jitsiApi); - console.log("iframe cowebsite", coWebsite?.iframe); - - if (!coWebsite) { - this.destroy(); - return; - } - - this.jitsiApi.addListener("videoConferenceLeft", () => { - this.closeOrUnload(coWebsite); - }); - - this.jitsiApi.addListener("readyToClose", () => { - this.closeOrUnload(coWebsite); - }); - } - public stop() { if (!this.jitsiApi) { return; @@ -276,8 +259,8 @@ class JitsiFactory { } } - private async loadJitsiScript(domain: string): Promise { - return new Promise((resolve, reject) => { + private loadJitsiScript(domain: string): CancelablePromise { + return new CancelablePromise((resolve, reject, cancel) => { if (this.jitsiScriptLoaded) { resolve(); return; @@ -296,6 +279,10 @@ class JitsiFactory { }; document.head.appendChild(jitsiScript); + + cancel(() => { + jitsiScript.remove(); + }); }); } } diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index de79047f..a926cb41 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -13,5 +13,6 @@ export enum DivImportance { } export const ON_ACTION_TRIGGER_BUTTON = "onaction"; +export const ON_ICON_TRIGGER_BUTTON = "onicon"; export type Box = { xStart: number; yStart: number; xEnd: number; yEnd: number }; diff --git a/front/src/i18n/de-DE/index.ts b/front/src/i18n/de-DE/index.ts index ab628a4d..40b63558 100644 --- a/front/src/i18n/de-DE/index.ts +++ b/front/src/i18n/de-DE/index.ts @@ -14,7 +14,7 @@ import warning from "./warning"; import woka from "./woka"; const de_DE: Translation = { - ...en_US, + ...(en_US as Translation), language: "Deutsch", country: "Deutschland", audio, diff --git a/front/src/i18n/de-DE/menu.ts b/front/src/i18n/de-DE/menu.ts index 5fb7882d..5e695d95 100644 --- a/front/src/i18n/de-DE/menu.ts +++ b/front/src/i18n/de-DE/menu.ts @@ -70,6 +70,7 @@ const menu: NonNullable = { description: "Link zu diesem Raum teilen!", copy: "Kopieren", share: "Teilen", + walk_automatically_to_position: "Walk automatically to my position", }, globalMessage: { text: "Text", diff --git a/front/src/i18n/en-US/index.ts b/front/src/i18n/en-US/index.ts index 2d3ac74a..b1a0e422 100644 --- a/front/src/i18n/en-US/index.ts +++ b/front/src/i18n/en-US/index.ts @@ -11,6 +11,7 @@ import menu from "./menu"; import report from "./report"; import warning from "./warning"; import emoji from "./emoji"; +import trigger from "./trigger"; const en_US: BaseTranslation = { language: "English", @@ -27,6 +28,7 @@ const en_US: BaseTranslation = { report, warning, emoji, + trigger, }; export default en_US; diff --git a/front/src/i18n/en-US/menu.ts b/front/src/i18n/en-US/menu.ts index b7c69935..0883fb15 100644 --- a/front/src/i18n/en-US/menu.ts +++ b/front/src/i18n/en-US/menu.ts @@ -70,6 +70,7 @@ const menu: BaseTranslation = { description: "Share the link of the room!", copy: "Copy", share: "Share", + walk_automatically_to_position: "Walk automatically to my position", }, globalMessage: { text: "Text", diff --git a/front/src/i18n/en-US/trigger.ts b/front/src/i18n/en-US/trigger.ts new file mode 100644 index 00000000..40e84bf4 --- /dev/null +++ b/front/src/i18n/en-US/trigger.ts @@ -0,0 +1,9 @@ +import type { BaseTranslation } from "../i18n-types"; + +const trigger: BaseTranslation = { + cowebsite: "Press SPACE or touch here to open web site", + jitsiRoom: "Press SPACE or touch here to enter Jitsi Meet room", + newTab: "Press SPACE or touch here to open web site in new tab", +}; + +export default trigger; diff --git a/front/src/i18n/fr-FR/index.ts b/front/src/i18n/fr-FR/index.ts index b9f91b13..77acbb4a 100644 --- a/front/src/i18n/fr-FR/index.ts +++ b/front/src/i18n/fr-FR/index.ts @@ -12,9 +12,10 @@ import menu from "./menu"; import report from "./report"; import warning from "./warning"; import woka from "./woka"; +import trigger from "./trigger"; const fr_FR: Translation = { - ...en_US, + ...(en_US as Translation), language: "Français", country: "France", audio, @@ -29,6 +30,7 @@ const fr_FR: Translation = { report, warning, emoji, + trigger, }; export default fr_FR; diff --git a/front/src/i18n/fr-FR/menu.ts b/front/src/i18n/fr-FR/menu.ts index 54481ec8..f8c58990 100644 --- a/front/src/i18n/fr-FR/menu.ts +++ b/front/src/i18n/fr-FR/menu.ts @@ -63,13 +63,14 @@ const menu: NonNullable = { }, fullscreen: "Plein écran", notifications: "Notifications", - cowebsiteTrigger: "Demander toujours avant d'ouvrir des sites web et des salles de réunion Jitsi", + cowebsiteTrigger: "Demander toujours avant d'ouvrir des sites web et des salles de conférence Jitsi", ignoreFollowRequest: "Ignorer les demandes de suivi des autres utilisateurs", }, invite: { description: "Partager le lien de la salle!", copy: "Copier", share: "Partager", + walk_automatically_to_position: "Marcher automatiquement jusqu'à ma position", }, globalMessage: { text: "Texte", diff --git a/front/src/i18n/fr-FR/trigger.ts b/front/src/i18n/fr-FR/trigger.ts new file mode 100644 index 00000000..5940e92a --- /dev/null +++ b/front/src/i18n/fr-FR/trigger.ts @@ -0,0 +1,9 @@ +import type { Translation } from "../i18n-types"; + +const trigger: NonNullable = { + cowebsite: "Appuyez sur ESPACE ou ici pour ouvrir le site Web", + jitsiRoom: "Appuyez sur ESPACE ou ici pour entrer dans la salle conférence Jitsi", + newTab: "Appuyez sur ESPACE ou ici pour ouvrir le site Web dans un nouvel onglet", +}; + +export default trigger; diff --git a/maps/tests/CoWebsite/cowebsite_property_trigger.json b/maps/tests/CoWebsite/cowebsite_property_trigger.json index 183fad3a..116f399c 100644 --- a/maps/tests/CoWebsite/cowebsite_property_trigger.json +++ b/maps/tests/CoWebsite/cowebsite_property_trigger.json @@ -47,6 +47,11 @@ "name":"openWebsiteTrigger", "type":"string", "value":"onaction" + }, + { + "name":"openWebsiteWidth", + "type":"int", + "value":100 }], "type":"tilelayer", "visible":true, diff --git a/maps/tests/DoorTest/map.json b/maps/tests/DoorTest/map.json new file mode 100644 index 00000000..b954b97c --- /dev/null +++ b/maps/tests/DoorTest/map.json @@ -0,0 +1,197 @@ +{ "compressionlevel":-1, + "height":30, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":1, + "name":"floor", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 0, 0, 17, 17, 17, 17, 17, 17, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 28, 28, 28, 0, 0, 28, 28, 28, 28, 28, 28, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":6, + "name":"furnitures", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":8, + "name":"closedPath", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "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, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":101.5, + "id":11, + "name":"", + "rotation":0, + "text": + { + "text":"You should be able to get here only if the path isn't blocked", + "wrap":true + }, + "type":"", + "visible":true, + "width":383, + "x":81.226595249185, + "y":297.048206800186 + }, + { + "height":101.5, + "id":12, + "name":"", + "rotation":0, + "text": + { + "text":"Try to move to the next room by using right-click \/ tap movement. Click \"Toggle Door\" button to block \/ make access.", + "wrap":true + }, + "type":"", + "visible":true, + "width":383, + "x":81, + "y":99.75 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":9, + "nextobjectid":13, + "orientation":"orthogonal", + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"..\/tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":27, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":28, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":72, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":73, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":30 +} \ No newline at end of file diff --git a/maps/tests/DoorTest/script.php b/maps/tests/DoorTest/script.php new file mode 100644 index 00000000..73f81836 --- /dev/null +++ b/maps/tests/DoorTest/script.php @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/maps/tests/index.html b/maps/tests/index.html index b4391fee..e8bf4369 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -32,6 +32,14 @@ Testing jitsiUrl property + + + Success Failure Pending + + + Test parallax effect + + Success Failure Pending @@ -40,6 +48,14 @@ Test animated tiles + + + Success Failure Pending + + + Test Doors + + Success Failure Pending diff --git a/maps/tests/move_to.json b/maps/tests/move_to.json index 796a9d46..0801831a 100644 --- a/maps/tests/move_to.json +++ b/maps/tests/move_to.json @@ -3,7 +3,7 @@ "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], + "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, 12, 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", @@ -204,6 +204,71 @@ "width":92.7120717279925, "x":233.848901257569, "y":135.845612785282 + }, + { + "height":0, + "id":9, + "name":"destination", + "point":true, + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":207.918025151374, + "y":243.31625523987 + }, + { + "height":45.0829063809967, + "id":10, + "name":"", + "rotation":0, + "text": + { + "halign":"center", + "text":"destination object", + "wrap":true + }, + "type":"", + "visible":true, + "width":83, + "x":167.26, + "y":254.682580344667 + }, + { + "height":19.6921, + "id":11, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start&moveTo=destination", + "wrap":true + }, + "type":"", + "visible":true, + "width":202.260327899394, + "x":32.2652715416861, + "y":148.51445302748 + }, + { + "height":19.6921, + "id":12, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=200,100", + "wrap":true + }, + "type":"", + "visible":true, + "width":202.26, + "x":32.2654354913834, + "y":169.008165183978 }], "opacity":1, "type":"objectgroup", @@ -212,7 +277,7 @@ "y":0 }], "nextlayerid":11, - "nextobjectid":9, + "nextobjectid":13, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.7.2", diff --git a/maps/tests/parallax.json b/maps/tests/parallax.json new file mode 100644 index 00000000..ce578090 --- /dev/null +++ b/maps/tests/parallax.json @@ -0,0 +1,150 @@ +{ "compressionlevel":-1, + "height":50, + "infinite":false, + "layers":[ + { + "data":[23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 12, 12, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 12, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 12, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 12, 12, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 12, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 12, 12, 12, 12, 23, 23, 23, 12, 12, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 12, 12, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 12, 12, 12, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 12, 12, 12, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23], + "height":50, + "id":8, + "name":"background_bottom", + "opacity":1, + "parallaxx":0, + "parallaxy":0, + "type":"tilelayer", + "visible":true, + "width":50, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":50, + "id":9, + "name":"background_middle", + "opacity":1, + "parallaxx":0.75, + "parallaxy":0.75, + "type":"tilelayer", + "visible":true, + "width":50, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 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, 12, 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, 12, 12, 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, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 12, 12, 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, 12, 12, 12, 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, 12, 12, 12, 12, 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, 12, 12, 12, 12, 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, 12, 12, 12, 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, 12, 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, 12, 12, 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, 12, 12, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":50, + "id":12, + "name":"background_top", + "opacity":1, + "parallaxx":0.5, + "parallaxy":0.5, + "type":"tilelayer", + "visible":true, + "width":50, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":50, + "id":10, + "name":"background_middle_items", + "opacity":1, + "parallaxx":0.75, + "parallaxy":0.75, + "type":"tilelayer", + "visible":true, + "width":50, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":50, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":50, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":50, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":50, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":19, + "id":5, + "name":"", + "rotation":0, + "text": + { + "text":"Walk around", + "wrap":true + }, + "type":"", + "visible":true, + "width":93, + "x":62.5, + "y":142.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 12, 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, 12, 12, 12, 12, 12, 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, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 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, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 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, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 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, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 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, 12, 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, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 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, 12, 12, 12, 12, 12, 12, 12, 12, 12, 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, 12, 12, 12, 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, 12, 12, 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, 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, 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, 12, 12, 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, 12, 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, 12, 12, 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, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 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, 12, 12, 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, 12, 12, 12, 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, 12, 12, 12, 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, 12, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 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, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 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, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 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, 12, 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, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 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, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0], + "height":50, + "id":13, + "name":"above", + "opacity":1, + "parallaxx":2, + "parallaxy":2, + "type":"tilelayer", + "visible":true, + "width":50, + "x":0, + "y":0 + }], + "nextlayerid":14, + "nextobjectid":7, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "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.6", + "width":50 +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b9698f61..adbed748 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,7 @@ # yarn lockfile v1 -"husky@^6.0.0": - "resolved" "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz" - "version" "6.0.0" +husky@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==