├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .node-version ├── .npmignore ├── .nvmrc ├── .prettierrc ├── .releaserc ├── CHANGELOG.md ├── README.md ├── commitlint.config.js ├── docs ├── dev │ ├── _category_.yml │ ├── functions-layers.md │ ├── functions-properties.md │ ├── functions-svelte.md │ ├── functions-variables.md │ └── index.md └── features │ ├── _category_.yml │ ├── automatic-configuration.md │ ├── bells.md │ ├── doors.md │ ├── generic-action-layers.md │ ├── images │ ├── areaBindings.png │ ├── autoopen.png │ ├── back_doorstep.png │ ├── bell_layer.png │ ├── bell_popup.png │ ├── bell_variable.png │ ├── close_door_layer.png │ ├── code_property.png │ ├── configuration_allowed_values.png │ ├── configuration_allowed_values_description.png │ ├── configuration_description.png │ ├── configuration_description_screenshot.png │ ├── configuration_label.png │ ├── configuration_label_screenshot.png │ ├── configuration_layers_tree.png │ ├── configuration_main_page.png │ ├── configuration_panel_global.png │ ├── configuration_panel_local.png │ ├── configuration_tag.png │ ├── configure_the_room_button.png │ ├── digicode.png │ ├── door_manual.mp4 │ ├── door_manual.png │ ├── door_variable.png │ ├── doorsteptag.png │ ├── front_doorstep.png │ ├── local_configuration_setup_tiled.png │ ├── open_door_layer.png │ ├── open_doors_auto.mov │ ├── open_doors_auto.mp4 │ ├── open_doors_auto.png │ ├── sample_configuration_screen.png │ ├── script.png │ ├── templated_property.png │ ├── variable.png │ ├── variables_in_configuration_layer.png │ └── visible_property.png │ ├── index.md │ ├── reference.md │ └── variable-to-property-binding.md ├── jest-base.js ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── resources ├── images │ ├── camera.svg │ ├── keys.gif │ ├── logo-message-pixel.png │ ├── micro.svg │ ├── mouse.gif │ └── tutov1 │ │ ├── discover │ │ ├── GlobalMessage.png │ │ ├── Object.png │ │ └── Silent.png │ │ ├── interact │ │ ├── Bubble.png │ │ ├── Lock.png │ │ └── ShareScreen.png │ │ ├── move │ │ ├── CamOFF.png │ │ ├── ZQSD.png │ │ └── wokamove.png │ │ ├── step1-onboarding.png │ │ ├── step1 │ │ ├── keyboard.svg │ │ ├── mouse.svg │ │ ├── right-click.svg │ │ └── touch.svg │ │ ├── step2-onboarding.png │ │ ├── step2 │ │ └── woka-meet.png │ │ ├── step3-onboarding.png │ │ ├── step3 │ │ └── woka-action.png │ │ ├── step4-onboarding.png │ │ ├── step5-onboarding.png │ │ ├── step6-onboarding.png │ │ └── welcome │ │ ├── Move.png │ │ ├── Talk.png │ │ └── laptop.png ├── sounds │ ├── LICENSE.txt │ ├── closing-door-1.mp3 │ ├── door-bell-1.mp3 │ ├── knocking-on-door.mp3 │ └── opening-door-1.mp3 └── videos │ └── firstConnection │ ├── bubble-zone-slower.mp4 │ ├── joystick-mobile.mp4 │ └── webcam-on-off.m4v ├── src ├── AreaObject.ts ├── Features │ ├── configuration.ts │ ├── default_assets_url.ts │ ├── doors.ts │ ├── properties_templates.ts │ ├── special_properties.ts │ ├── tutorialv1.ts │ ├── variable_actions.ts │ └── workadventure_assets_url.ts ├── Iframes │ ├── Configuration │ │ ├── Components │ │ │ ├── App.svelte │ │ │ ├── Field.svelte │ │ │ ├── Section.svelte │ │ │ └── Sections.svelte │ │ ├── Stores │ │ │ ├── LayersStore.ts │ │ │ ├── NavigationStore.ts │ │ │ └── VariablesStore.ts │ │ ├── configuration.ejs │ │ ├── index.ts │ │ └── style │ │ │ └── style.scss │ ├── Keypad │ │ ├── index.ts │ │ └── keypad.ejs │ └── TutorialV1 │ │ ├── Launcher │ │ ├── index.ts │ │ └── script.ejs │ │ └── Tuto │ │ ├── Components │ │ ├── App.svelte │ │ ├── Steps.svelte │ │ └── Steps │ │ │ ├── Step1.svelte │ │ │ ├── Step2.svelte │ │ │ ├── Step3.svelte │ │ │ └── Welcome.svelte │ │ ├── Store │ │ └── StepStore.ts │ │ ├── index.ts │ │ ├── style │ │ └── index.scss │ │ └── tutorialv1.ejs ├── LayersExtra.ts ├── LayersFlattener.ts ├── Properties.ts ├── TemplateValue.ts ├── VariableMapper.ts ├── VariablesExtra.ts ├── bootstrap.ts ├── index.ts ├── init.ts ├── translate │ ├── en-US │ │ ├── index.ts │ │ └── tuto.ts │ └── fr-FR │ │ ├── index.ts │ │ └── tuto.ts └── types.d.ts ├── tailwind.config.cjs ├── tailwind.config.js ├── test ├── maps │ ├── Door2_pipo.png │ ├── configuration.json │ ├── configuration_exits.json │ ├── configuration_filters.json │ ├── configuration_multi_sections.json │ ├── configuration_persist_test.json │ ├── doors.json │ ├── doors_with_autodoorstep.json │ ├── doors_with_code.json │ ├── doors_with_object_doorsteps.json │ ├── index.ejs │ ├── special-zones.png │ ├── tileset1.png │ ├── tileset2.png │ └── walls.png ├── setup-test.js └── unit │ └── TemplateValue.spec.ts ├── tsconfig-for-webpack.json ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.spec.json └── webpack.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # Top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 4 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.txt] 16 | trim_trailing_whitespace = false 17 | 18 | [*.json] 19 | insert_final_newline = false 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | jest.config.js 3 | tailwind.config.js 4 | tailwind.config.cjs 5 | postcss.config.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rpidanny/eslint-config-typescript", 3 | "rules": { 4 | "no-unused-vars": "off", 5 | "simple-import-sort/imports": "off", 6 | "arrow-parens": "off", 7 | "camelcase": "off", 8 | "no-throw-literal": "error" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: 'npm' 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: '/' 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: 'daily' 10 | rebase-strategy: "auto" 11 | labels: 12 | - "security" 13 | - "dependencies" 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | build-and-test: 10 | name: Build and Test 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: '16.x' 20 | registry-url: 'https://registry.npmjs.org' 21 | 22 | - run: npm ci 23 | - run: npm run lint 24 | - run: npm test 25 | - run: npm run build --if-present 26 | 27 | - name: Code Coverage 28 | run: curl -s https://codecov.io/bash | bash 29 | env: 30 | CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - 'alpha' 8 | - 'beta' 9 | - '*.x' 10 | - '*.*.x' 11 | 12 | jobs: 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: '18.x' 23 | registry-url: 'https://registry.npmjs.org' 24 | 25 | - run: npm ci 26 | - run: npm run lint 27 | - run: npm run semantic-release-dry-run 28 | env: 29 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 30 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 31 | CI: true 32 | # Grab the tag number by running semantic-release in dry mode and put the result in release_tag env variable 33 | - run: echo "release_tag=$(npm run semantic-release-dry-run | grep -Po 'The next release version is \K([\w.-]*)')" >> $GITHUB_ENV 34 | env: 35 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 36 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | CI: true 38 | - run: npm run test 39 | #- run: sed -i 's|""|"https://cdn.jsdelivr.net/npm/@workadventure/scripting-api-extra@${{ env.release_tag }}/dist"|g' src/Features/default_assets_url.ts 40 | - run: sed -i 's|""|"https://unpkg.com/@workadventure/scripting-api-extra@${{ env.release_tag }}/dist"|g' src/Features/default_assets_url.ts 41 | - run: sed -i 's|""|"https://admin.workadventu.re/html"|g' src/Features/workadventure_assets_url.ts 42 | - run: npm run build --if-present 43 | 44 | # Deployment on Github Pages 45 | - name: "Deploy Github Pages" 46 | uses: JamesIves/github-pages-deploy-action@v4 47 | if: github.ref == 'refs/heads/main' 48 | with: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | BRANCH: gh-pages # <- Branche sur laquelle seront commités les fichiers 51 | FOLDER: dist/ # <- Dossier contenant notre documentation générée 52 | BASE_BRANCH: main 53 | 54 | # Code coverage 55 | #- name: "Code coverage" 56 | # run: curl -s https://codecov.io/bash | bash 57 | # env: 58 | # CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} 59 | 60 | # Deployment on npm 61 | - name: "Deploy NPM" 62 | run: npm run semantic-release 63 | env: 64 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 65 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 66 | CI: true 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | .env*.local 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | .parcel-cache 84 | 85 | # Next.js build output 86 | .next 87 | 88 | # Nuxt.js build / generate output 89 | .nuxt 90 | dist/* 91 | !dist/sounds 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | # Stores VSCode versions used for testing VSCode extensions 115 | .vscode-test 116 | 117 | # End of https://www.toptal.com/developers/gitignore/api/node 118 | 119 | .DS_Store 120 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 12.18.2 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | examples/ 3 | test/ 4 | *.js 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.18.2 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "singleQuote": false, 4 | "printWidth": 100, 5 | "tabWidth": 4 6 | } 7 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ['+([0-9])?(.{+([0-9]),x}).x', 'main', 'next', 'next-major', {name: 'beta', prerelease: true}, {name: 'alpha', prerelease: true}], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | "@semantic-release/changelog", 7 | "@semantic-release/npm", 8 | ["@semantic-release/github", { 9 | "assets": [] 10 | }], 11 | [ 12 | "@semantic-release/git", 13 | { 14 | "assets": [ 15 | "package.json", 16 | "package-lock.json", 17 | "CHANGELOG.md" 18 | ], 19 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 20 | } 21 | ] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WorkAdventure Scripting API Extra features 2 | 3 | ![Github Actions](https://github.com/workadventure/scripting-api-extra/workflows/Release/badge.svg) [![codecov](https://codecov.io/gh/workadventure/scripting-api-extra/branch/main/graph/badge.svg?token=UCCA6D6JCB)](https://codecov.io/gh/workadventure/scripting-api-extra) 4 | 5 | This NPM package contains a set of reusable utility functions and features that can be used to enhance WorkAdventure maps. 6 | 7 | ## Usage 8 | 9 | There are many ways to import the `@workadventure/scripting-api-extra` package. 10 | 11 | ### Importing directly in a map 12 | 13 | If you only want to use the extra "features", you can directly import the package in your map, 14 | by adding a "script" property at the map level, pointing to the "bundled" package: 15 | 16 | `script: https://unpkg.com/@workadventure/scripting-api-extra@^1/dist/bundle.js` 17 | 18 | Please note that you can change the version number of the package in the URL. 19 | 20 | ### Importing in your application / own scripts 21 | 22 | If you are developing your own scripts, you can import the library using NPM. 23 | 24 | ``` 25 | npm install --save @workadventure/scripting-api-extra 26 | ``` 27 | 28 | ## Table of content 29 | 30 | ### Features 31 | 32 | - [Doors](docs/doors.md) 33 | - [Bells](docs/bells.md) 34 | - [Generic action layers](docs/generic-action-layers.md) 35 | 36 | ### Functions 37 | 38 | - [`Properties`](docs/functions-properties.md) related functions (utility functions to acces properties...) 39 | - [`Variables`](docs/functions-variables.md) related functions (access variables metadata...) 40 | - [`Layers`](docs/functions-layers.md) related functions (get a list of all layers, find layers boundaries...) 41 | 42 | ## Contributing 43 | 44 | ```console 45 | # install dependencies 46 | $ npm install 47 | 48 | # run unit tests 49 | $ npm run tests 50 | 51 | # run integration tests 52 | $ npm run start # then browse to http://localhost:3000/test/maps/ 53 | ``` 54 | 55 | Note: `npm run start` will connect to `play.workadventu.re` to server WorkAdventure. 56 | 57 | If for development purpose, you want to connect to a development WorkAdventure server, you can use the `WORKADVENTURE_URL` environment variable: 58 | 59 | ```console 60 | $ WORKADVENTURE_URL="http://play.workadventure.localhost" npm run start 61 | ``` 62 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { extends: ["@commitlint/config-conventional"] }; 4 | -------------------------------------------------------------------------------- /docs/dev/_category_.yml: -------------------------------------------------------------------------------- 1 | position: 40 2 | collapsible: true # make the category collapsible 3 | collapsed: true # keep the category open by default 4 | label: Extended utility functions 5 | -------------------------------------------------------------------------------- /docs/dev/functions-layers.md: -------------------------------------------------------------------------------- 1 | # Tiled Layers 2 | 3 | :::info Important 4 | To use these functions, you need to [import the "Scripting API Extra"](index.md#importing-the-utility-functions) library. 5 | ::: 6 | 7 | ### Return a Map of all layers 8 | 9 | Layers can be nested in group layers. 10 | 11 | The `getLayersMap()` function returns a map of all layers in a uni-dimensional map. 12 | 13 | Layers are **renamed**: if they are in a group layer, the name of the group layer is prepended with a "/" as a separator. 14 | 15 | Layers are indexed by name. 16 | 17 | ```typescript 18 | import {getLayersMap} from '@workadventure/scripting-api-extra'; 19 | 20 | const layers = await getLayersMap(); 21 | 22 | // Access a layer directly by name 23 | const mylayer = layers.get('my_layer'); 24 | 25 | // Iterate over all layers 26 | for (const layer of layers.values()) { 27 | // ... 28 | } 29 | ``` 30 | 31 | ### Get boundaries of a layer 32 | 33 | `findLayerBoundaries` returns the boundaries of a given layer as an object with properties: { top: number, left: number, right: number, bottom: number } 34 | 35 | Numbers are expressed in "tiles", not pixels. 36 | 37 | ``` 38 | findLayerBoundaries(layer: ITiledMapTileLayer): { 39 | top: number; 40 | left: number; 41 | right: number; 42 | bottom: number; 43 | } 44 | ``` 45 | 46 | Example: 47 | 48 | ```typescript 49 | import {getLayersMap, findLayerBoundaries} from '@workadventure/scripting-api-extra'; 50 | import {ITiledMapTileLayer} from "@workadventure/tiled-map-type-guard/dist/ITiledMapTileLayer"; 51 | 52 | const layers = await getLayersMap(); 53 | 54 | const layer = layers.get("my_layer") as ITiledMapTileLayer; 55 | 56 | const boundaries = findLayerBoundaries(layer); 57 | console.log('Top:' , boundaries.top); 58 | console.log('Left:' , boundaries.left); 59 | console.log('Bottom:' , boundaries.bottom); 60 | console.log('Right:' , boundaries.right); 61 | ``` 62 | 63 | ### Get boundaries of several layers 64 | 65 | If you are looking for the boundaries of several layers at once, you can use the `findLayersBoundaries` variant. 66 | 67 | ``` 68 | findLayersBoundaries(layers: ITiledMapTileLayer[]): { 69 | top: number; 70 | left: number; 71 | right: number; 72 | bottom: number; 73 | } 74 | ``` 75 | 76 | It will return a square containing all the tiles of all the layers passed in parameters. 77 | 78 | -------------------------------------------------------------------------------- /docs/dev/functions-properties.md: -------------------------------------------------------------------------------- 1 | # Tiled Properties 2 | 3 | :::info Important 4 | To use these functions, you need to [import the "Scripting API Extra"](index.md#importing-the-utility-functions) library. 5 | ::: 6 | 7 | In your Tiled map, a number of items can have "properties" (the map itself, layers, tiles, tilesets, objects...). 8 | 9 | The JSON map can be fetched using `WA.room.getTiledMap`. But when it comes to analyzing the JSON map, you are on your own. 10 | 11 | The Scripting API Extra package comes with a useful `Properties` class to analyze these properties. 12 | 13 | Usage: 14 | 15 | ```typescript 16 | const map = await WA.room.getTiledMap(); 17 | 18 | const mapProperties = new Properties(map.properties); 19 | 20 | // getOne fetches the value of the property passed in parameter. 21 | const name = mapProperties.get('name') as string; 22 | 23 | // getString is the same as get except it ensures the value is a string (and throws an exception if it is not) 24 | const name = mapProperties.getString('name'); 25 | ``` 26 | 27 | Methods available: 28 | 29 | ```typescript 30 | class Properties { 31 | get(name: string): string | boolean | number | undefined; // returns the property 32 | getString(name: string): string | undefined; // returns the property (and checks it is a string) 33 | getNumber(name: string): number | undefined; // returns the property (and checks it is a number) 34 | getBoolean(name: string): boolean | undefined; // returns the property (and checks it is a boolean) 35 | mustGetString(name: string): string; // returns the property as a string (throws an Error if not found) 36 | mustGetNumber(name: string): string; // returns the property as a number (throws an Error if not found) 37 | mustGetBoolean(name: string): string; // returns the property as a boolean (throws an Error if not found) 38 | getType(name: string): string | undefined; // returns the type of property (as defined in the map) 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/dev/functions-svelte.md: -------------------------------------------------------------------------------- 1 | # Svelte 2 | 3 | :::info Important 4 | To use these functions, you need to [import the "Scripting API Extra"](index.md#importing-the-utility-functions) library. 5 | ::: 6 | 7 | If you happen to use the [Svelte framework](https://svelte.dev/), the Scripting API Extra package provide some 8 | utility functions to easily bind your Svelte components to your WorkAdventure map. 9 | 10 | ## Mapping a WorkAdventure variable to a Svelte store 11 | 12 | Use `createStoreFromVariable` to create a Svelte store that is bound to a WorkAdventure variable. 13 | 14 | ```typescript 15 | const myVariableStore = createStoreFromVariable('my_variable'); 16 | ``` 17 | 18 | `createStoreFromVariable` returns a `writable` store. 19 | 20 | If you already have a Svelte store and you want to bind it to a WorkAdventure variable, use `mapVariableToStore`. 21 | 22 | ```typescript 23 | // Maps a WorkAdventure variable to an existing store. 24 | // The "myVariableStore" must be a Svelte store with a "set" function. 25 | mapVariableToStore('my_variable', myVariableStore); 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/dev/functions-variables.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | :::info Important 4 | To use these functions, you need to [import the "Scripting API Extra"](index.md#importing-the-utility-functions) library. 5 | ::: 6 | 7 | ## Return a list of all variables defined in the map 8 | 9 | ``` 10 | // returns a list of all the variables defined in the map. 11 | getAllVariables(): Promise> 12 | ``` 13 | 14 | Variables are returned as a Map. The key is the name of the variable, the value is an object representing the variable. 15 | You can fetch individual properties defined in Tiled for this variable using this object's `properties` attribute. 16 | 17 | For instance: 18 | 19 | ```typescript 20 | import { getAllVariables, VariableDescriptor } from '@workadventure/scripting-api-extra'; 21 | 22 | const variables = await getAllVariables(); 23 | console.log(variables['my_variable'].properties.getOne('persist')); 24 | ``` 25 | 26 | Note: the `VariableDescriptor` class returned does not contain the value of the variable itself. It represents the 27 | variable object as defined in the Tiled map. This can be useful to access additional metadata that can be stored 28 | in special properties of the variable, or to access the variable "position" in the map (since a variable is represented 29 | by a "Point" object in a map). 30 | 31 | ```typescript 32 | class VariableDescriptor { 33 | // the name of the variable 34 | name: string 35 | // an object representing the properties of the variable 36 | properties: Properties 37 | // The position of the variable 38 | x: number 39 | y: number 40 | // True if the variable can be read by the current player 41 | isReadable: boolean 42 | // True if the variable can be written by the current player 43 | isWritable: boolean 44 | } 45 | ``` 46 | 47 | ## Configuration panel 48 | 49 | ### Opening the local configuration panel 50 | 51 | ``` 52 | // Signature of the function 53 | function openConfig(variables?: string[]): void 54 | ``` 55 | 56 | You can open the local configuration panel inside an iFrame just like you do via the Tiled `openConfig` property. 57 | This property let you filter which variables you want to display in the configuration page. 58 | 59 | Well, you can do exactly that thanks to the `openConfig()` function. Here's how it works: 60 | 61 | ```typescript 62 | import {openConfig} from '@workadventure/scripting-api-extra'; 63 | 64 | // This will open the local configuration panel with all the variables in the Tiled 'configuration' layer. 65 | openConfig(); 66 | ``` 67 | 68 | ### Filtering the local configuration panel 69 | 70 | You can filter which variables to display by passing an array of variable names to the function. 71 | 72 | ```typescript 73 | import {openConfig} from '@workadventure/scripting-api-extra'; 74 | 75 | // This will open the local configuration panel with the specified variables in the Tiled 'configuration' layer. 76 | openConfig(['leftDoorExit']); 77 | ``` 78 | 79 | Here is a quick example of how you can edit a variable by walking near the object that represents it in your map: 80 | 81 | ```typescript 82 | import { openConfig } from '@workadventure/scripting-api-extra'; 83 | 84 | WA.room.onEnterLayer('leftDoorStep').subscribe(() => { 85 | openConfig(['leftDoorExit']); 86 | }); 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/dev/index.md: -------------------------------------------------------------------------------- 1 | # Extended Utility Functions 2 | 3 | The utility functions described in this section are not part of WorkAdventure core solution. 4 | Instead, they are provided in a separate NPM package called [Scripting API Extra](https://github.com/workadventure/scripting-api-extra). 5 | 6 | In this section, you will find a list of these extended functions. 7 | 8 | ## Importing the "utility functions" 9 | 10 | Import these functions in your project using NPM: 11 | 12 | ``` 13 | npm install --save @workadventure/scripting-api-extra 14 | ``` 15 | 16 | Note: these functions are provided as an ES6 module. You will need a "bundler" (like Webpack) to package your script. 17 | If you don't have a bundler already, have a look at the [WorkAdventure Map Starter Kit](https://github.com/workadventure/map-starter-kit/). 18 | It provides a complete development environment ready with Typescript enabled. 19 | 20 | For a complete installation guide here, check the "[About the extended features](index.md#importing-the-scripting-api-extra-library)" documentation 21 | -------------------------------------------------------------------------------- /docs/features/_category_.yml: -------------------------------------------------------------------------------- 1 | position: 110 2 | label: 'Extra Features' 3 | collapsible: true # make the category collapsible 4 | collapsed: true # keep the category open by default 5 | -------------------------------------------------------------------------------- /docs/features/automatic-configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sidebar_position: 30 4 | title: Map configuration screen 5 | 6 | --- 7 | 8 | # Generating automatically a configuration screen 9 | 10 | :::info 11 | To generate a configuration screen automatically, you need to [import the "Scripting API Extra" script in your map](/developer/map-scripting/scripting-api-extra/#importing-the-extended-features) 12 | ::: 13 | 14 | WorkAdventure comes with a ["variables"](/developer/map-scripting/references/api-state) system that can be used 15 | to change a map dynamically. Variables can have an impact on a map through [property bindings](variable-to-property-binding), 16 | or through the [Scripting API](/developer/map-scripting/references/api-state). 17 | 18 | In order to edit the value of a variable, the *Scripting API Extra* library comes with a way to define configuration 19 | pages automatically. 20 | 21 | The configuration page displays a form that is **generated from the variables** present on the map. 22 | Each variable is mapped to one field in the form. 23 | 24 | For a variable to appear in the configuration panel, it MUST be stored in a layer called `configuration`. 25 | 26 | ![List of variables that will be displayed in the configuration screen](images/variables_in_configuration_layer.png) 27 | 28 | Below, we will see the two ways of configuring your variables with the configuration panel. 29 | 30 | ## Global configuration panel 31 | 32 | Having a variable inside a `configuration` object layer will automatically add a button inside the menu, called 'Configure the room'. 33 | 34 | ![Configure the room button](images/configure_the_room_button.png) 35 | 36 | Try to open the menu and check for this new sub-menu. By clicking on the button you should now see the global configuration panel: 37 | 38 | ![Global configuration panel](images/configuration_panel_global.png) 39 | 40 | We call it 'global' because it contains all the variables that the `configuration` layer have. 41 | This is great if you do not have many variables to configure and if they are well organized. 42 | Now, it would be great to see only the variable that interests you in this panel, for example by going in front of a door to open or a website to configure. 43 | We call this 'local' configuration, and you will see it in action right away! 44 | 45 | ## Local configuration panel 46 | 47 | By going in front of this door, the configuration form will contain only the variable that controls the right door: 48 | 49 | ![Local configuration panel](images/configuration_panel_local.png) 50 | 51 | Comparing to the previous screenshot you see only one field, and it's the field that can set the exit URL of this specific door. 52 | To be able to achieve that, all you need to do is to create a layer with the `openConfig` property with the name of the variable to configure as its value. 53 | Because our variable here is called `rightDoorExitUrl` we added `openConfig: rightDoorExitUrl`. 54 | 55 | :::info Pro tip 56 | Note that you can tell the `openConfig` property to include multiple variables by separating the variable names by a comma. 57 | For example: `openConfig: rightDoorExitUrl,leftDoorExitUrl`. 58 | Also, the layer containing openConfig must have a `zone` (string) property as well, but this step will be removed in a future version. 59 | ::: 60 | 61 | ![Local configuration setup](images/local_configuration_setup_tiled.png) 62 | 63 | You can see that our layer is represented in the game by a single tile (the yellow one) and that `openConfig` is very similar to `openWebsite`! 64 | In fact, it has technically the same effect of opening an iframe, but you can't control the website that will appear, only the number of variables. 65 | 66 | If you set `openConfigTrigger: onaction`, when the user walks on the layer, an alert message will be displayed at the bottom of the screen. 67 | If you set `openConfigTriggerMessage: your message action` you can edit the alert message displayed. If not defined, the default message will be 'Press SPACE or touch here to configure'. 68 | 69 | ## Protecting the configuration screen 70 | 71 | By default, the configuration screen will be accessible to anyone. You will probably want to restrict the access of the 72 | configuration screen to users that have a certain *tag*. 73 | 74 | To do this with the global configuration panel, simply add a `tag` property to the configuration object layer. The value of the property is the name of the tag 75 | that users must have to access the configuration screen. 76 | 77 | ![Here, only users with tag "admin" will have access to the configuration screen](images/configuration_tag.png) 78 | 79 | _Here, only users with tag "admin" will have access to the configuration screen_ 80 | 81 | You can also protect a local configuration zone by adding the `openConfigAdminTag` property and by setting a tag as value. 82 | Adding `openConfigAdminTag: admin` to a layer that contains `openConfig` will prevent players that don't have the 'admin' tag to see the local configuration panel, as well as the alert to open it. 83 | 84 | ## Altering the display of a variable 85 | 86 | ### Changing the label 87 | 88 | Each variable is mapped to one field in the form. 89 | 90 | By default, the name of the variable is used as the label. 91 | 92 | You can add a `label` property on the variable to display a custom label for your variable. 93 | 94 | ![The label property added to a variable](images/configuration_label.png) 95 | 96 | ![The label property is used as field label](images/configuration_label_screenshot.png) 97 | 98 | ### Changing the type of the field 99 | 100 | By default, a variable will be displayed as a text box, unless the `default` property is a boolean, in which case 101 | it will be displayed as a checkbox. 102 | 103 | You can alter this type of the field displayed by using the `type` **custom** property. 104 | 105 | :::caution Important! 106 | The *type* of the point object that represents the variable must always be `variable`. You should add 107 | a **custom** property whose name is "type" to set the type of the field. 108 | ::: 109 | 110 | Acceptable values for the "type" property are: 111 | 112 | - `text`: displays a text field 113 | - `checkbox`: displays a checkbox 114 | - `select`: displays a select (see `allowed_values`) 115 | - `radio`: displays radio buttons (see `allowed_values`) 116 | 117 | ### Enumerations (select / radio buttons) 118 | 119 | If you want to display a select box or radio buttons, you need to provide the list of possible values. 120 | This can be done through the `allowed_values` property. 121 | 122 | The `allowed_values` must be passed a JSON object whose keys are the text displayed, and whose values are the value that 123 | will take the variable if the option is selected. 124 | 125 | For instance, if you want to do a simple "Yes/No" radio button, you would write: 126 | 127 | `allowed_values: { 128 | "Yes": true, 129 | "No": false 130 | }` 131 | 132 | When you use the `allowed_values` property in your variable, do not forget to the the `type` property to `select` or `radio`. 133 | 134 | ![The "allowed_values" property added to a variable](images/configuration_allowed_values.png) 135 | 136 | _The "allowed_values" property added to a variable_ 137 | 138 | ![The field is displayed as a "select" because we chose "type = select"](images/configuration_allowed_values_description.png) 139 | 140 | _The field is displayed as a "select" because we chose "type = select"_ 141 | 142 | 143 | ### Adding a description / hint 144 | 145 | You can add a `description` property on the variable to display a description of the purpose of the field, below 146 | the field. 147 | 148 | ![The description property added to a variable](images/configuration_description.png) 149 | 150 | _The description property added to a variable_ 151 | 152 | ![The description property is displayed below the field](images/configuration_description_screenshot.png) 153 | 154 | _The description property is displayed below the field_ 155 | 156 | ### Field visibility 157 | 158 | The configuration page will respect the visibility rights configured on the variable. 159 | 160 | - If the `readableBy` property is set on the variable, the variable will appear in the configuration screen only if the current user has the right 161 | to read this variable. 162 | - If the `writableBy` property is set on the variable, the variable will be displayed, but modifiable only if the current user has the right 163 | to write to this variable. 164 | 165 | ## Creating sub-sections 166 | 167 | Do you have many variables on your map? You can organize these variables on different configuration pages. 168 | To do this, simply turn the `configuration` layer into a "group" layer. In this group layer, you can put many object 169 | layers. Each object layer will be rendered in a different page. 170 | 171 | ![The configuration layer is now a group layer](images/configuration_layers_tree.png) 172 | 173 | _The configuration layer is now a group layer_ 174 | 175 | ![Each configuration page is accessible from the main page](images/configuration_main_page.png) 176 | 177 | Each configuration page is accessible from the main page using a menu made of buttons. 178 | 179 | The label of the buttons can be edited by setting a custom `label` property on each object layer inside the "configuration" layer. 180 | -------------------------------------------------------------------------------- /docs/features/bells.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sidebar_position: 20 4 | title: Bells / Knocking on a door 5 | 6 | --- 7 | 8 | 9 | # Bells / Knocking on a door 10 | 11 | :::info Important 12 | To use the "bells" feature, you need to [import the "Scripting API Extra" script in your map](/developer/map-scripting/scripting-api-extra/#importing-the-extended-features) 13 | ::: 14 | 15 | Bells are usually used next to [doors](doors) (even if they can be used independently) 16 | 17 | You can: 18 | 19 | - decide if the bell will ring automatically or by pressing a button 20 | - decide the sound of the bell (or a knocking on the door sound if you prefer) 21 | - decide if the bell can be heard on all the map, or only at a given radius around the bell 22 | 23 | ## The bell variable 24 | 25 | In order to create a bell, you need to create a ["variable"](/developer/map-scripting/references/api-state) that will 26 | be used to share the fact the bell is ringing. 27 | The value of the variable has no importance, it is just used to propagate the event that the bell is ringing. 28 | 29 | In order to add a variable, you need to create a "Point" on any "object layer" in your map, in Tiled. 30 | 31 | Unlike with classical variables, the position of the variable object is important. The sound will be emitted from this point. 32 | 33 | - You can give this variable any name. 34 | - The "type" of the object MUST be "variable". 35 | - You MUST define a custom boolean property named `bell`. The "bell" checkbox must be checked. 36 | 37 | Then add 2 properties 38 | 39 | - `bellSound`: URL of the sound of the bell ringing (you can also use a knock-knock-knock sound if you have a door :) ) 40 | - `soundRadius` (optional): The radius at which one can hear the sound (expressed in pixels, the sound center being **the position of the variable**) 41 | 42 | The farther you are from the sound center, the less you will hear the sound. If you don't set any soundRadius, the whole 43 | map will hear the sound. 44 | 45 | The URL of the sound can be absolute or relative. If you choose a relative URL, it is is relative to the map. 46 | 47 | ![Bell Variable](images/bell_variable.png) 48 | 49 | ## The bell display layer 50 | 51 | Now, we need to define the position on the map from where the bell sound can be triggered. 52 | 53 | Add a tile layer in your map. 54 | 55 | On the layer add this property: 56 | 57 | - `bellVariable`: (Compulsory) the name of the "bell" variable that will be triggered when someone walks on this layer 58 | 59 | With only those 2 properties, whenever a user walks into the layer, the bell will automatically ring. 60 | 61 | ## Adding a bell button 62 | 63 | Of course, most of the time, you want the bell to be triggered by a manual user interaction (pressing the bell button). 64 | 65 | To add this button, you should create a "rectangle" object on any object layer in Tiled. The button will appear within this 66 | rectangle object on the map. 67 | 68 | Give this rectangle object any name. 69 | 70 | ![Bell Popup](images/bell_popup.png) 71 | 72 | Now, on the bell layer, create 2 properties: 73 | 74 | - `bellPopup`: the name of a rectangle object on the object layer in the map that will display the "Ring" button to ring the bell. 75 | - `bellButtonText`: the text to display in the button to ring the bell. Defaults to "Ring" 76 | 77 | ![Bell Layer](images/bell_layer.png) 78 | 79 | :::info Pro tip 80 | the `bellButtonText` can contain emojis. So you can use the "bell" emoji in the text: 🔔 81 | ::: 82 | -------------------------------------------------------------------------------- /docs/features/doors.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sidebar_position: 10 4 | title: Adding doors 5 | 6 | --- 7 | 8 | 9 | # Doors 10 | 11 | :::info Important 12 | To use the "doors" feature, you need to [import the "Scripting API Extra" script in your map](/developer/map-scripting/scripting-api-extra/#importing-the-extended-features) 13 | ::: 14 | 15 | Doors are extremely customizable in your map. You can: 16 | 17 | - decide to open/close the door automatically or on user interaction 18 | - decide who can operate the door (based on the user's tags) 19 | - lock a door with a digit code 20 | - you can even add a bell (look at the [bells](bells) dedicated document) 21 | 22 | In order to create a door, you will need: 23 | 24 | - a tileset containing sprites for the door in an opened and in a closed state 25 | - optionally 2 MP3 files containing the opening and closing sound 26 | 27 | :::info 28 | Looking for cool door sprites? Checkout out [door sprites by Pipoya on itch.io](https://pipoya.itch.io/pipoya-rpg-tileset-32x32/devlog/222435/add-door-animation) 29 | ::: 30 | 31 | Doors are made of 1 variable and 4 layers. We will see how to build one step by step in this document. 32 | 33 | ## The Open / Close Layers 34 | 35 | The first step is to add 2 layers on your map. One will contain the opened door and one the closed door. 36 | 37 | ![Close Door Layer](images/close_door_layer.png) 38 | 39 | ![Open Door Layer](images/open_door_layer.png) 40 | 41 | :::info 42 | If you have complex doors that span on several layers, that is fine. 43 | ::: 44 | 45 | :::caution 46 | Do not forget to make the closed door tiles "collidable" (using the `collides` property), otherwise users will be able 47 | to walk through your open door! 48 | ::: 49 | 50 | ## Door Variable 51 | 52 | Then, add a ["variable"](https://docs.workadventu.re/developer/map-scripting/references/api-state). The variable will control 53 | the "state" of the door and to share that state between the players. If the variable value is `true`, 54 | the door will be open, if it is `false`, the door will be closed. 55 | 56 | In order to add a variable, you need to create a "Point" on any "object layer" in your map, in Tiled. 57 | 58 | ![Door Variable](images/door_variable.png) 59 | 60 | - You can give this variable any name. 61 | - The "type" of the object MUST be "variable". 62 | - You MUST define a custom boolean property named `door`. The "door" checkbox must be checked. 63 | - You can set the `default` custom property to `true` (opened by default) or `false` (closed by default). This will be used 64 | the first time a user enters the map. 65 | - You can set the `persist` custom property to `true` if you want to save the state of the door. Otherwise, the door state 66 | will reset to the `default` property when the room is empty. 67 | 68 | Now, add 2 properties to the variable: 69 | 70 | - `openLayer`: this MUST contain the name of the opened door layer 71 | - `closeLayer`: this MUST contain the name of the closed door layer 72 | 73 | :::info Note 74 | If your door is spanning over several layers, for both the `openLayer` and `closeLayer` properties, you can input 75 | several layer names separated by a new line character. 76 | ::: 77 | 78 | ### Opening / Closing Sound 79 | 80 | You can add an opening or closing sound to the door by using the `openSound` or `closeSound` properties of the door variable. 81 | 82 | The value of these properties should be a URL to a MP3 file of a sound opening or closing the door. 83 | 84 | - `openSound`: URL of the sound of a door opening 85 | - `closeSound`: URL of the sound of a door closing 86 | 87 | Anybody on the map will hear the sound of the door opening or closing. 88 | 89 | If you want to limit the sound to a certain area, you can use the `soundRadius` property. 90 | `soundRadius` is expressed in pixels. If you are further than `soundRadius` pixels from the center of the door, 91 | you will not hear the door opening or closing. Also, the further you are, the fainter the sound. 92 | 93 | :::info 94 | If you are looking for opening/closing door sounds, [FesliyanStudios provides a variety of sounds](https://www.fesliyanstudios.com/royalty-free-sound-effects-download/opening-closing-door-54) 95 | ::: 96 | 97 | ## Door Steps 98 | 99 | So far, our door is controlled by the state of a variable. Unless you are mastering the scripting API, you 100 | need a way to change the "state" of the door variable. This is what "door steps" are for! 101 | 102 | Door steps are layers or area objects (usually one in front of the door and one behind) that control the opening / closing of the door. 103 | 104 | ![Front Door Step](images/front_doorstep.png) 105 | 106 | ![Back Door Step](images/back_doorstep.png) 107 | 108 | :::info One or two door steps? 109 | Do you want to have different rules for opening / closing the door depending on the side 110 | of the door you are standing? If yes, you will need 2 door steps (and therefore, 2 layers or 2 area objects). For instance, you can have the back 111 | door step that triggers automatically the opening of the door, while the front door step requires to enter a code to open the door. 112 | One door step layer spanning the 2 sides of the door can be enough if your door behaves the same on both sides. 113 | ::: 114 | 115 | In order to create a door step, you MUST create an additional layer or an area object and put the following property on it: 116 | 117 | - `doorVariable` (string): the name of the door variable that this door step controls 118 | 119 | You should see something similar to the video below: 120 | 121 | import ReactPlayer from 'react-player' 122 | 123 | 124 | 125 | ### Automatically VS manually opening the door 126 | 127 | By default, when a player walks next to the door, a user interaction (pressing SPACE) is required to open or close the 128 | door. 129 | 130 | You can change this behaviour by setting the `autoOpen` and `autoClose` boolean properties on the **door step layer**. 131 | 132 | By setting `autoOpen` to true, the door will automatically open when someone walks in the door step layer. 133 | By setting `autoClose` to true, the door will automatically close when someone walks out of the door step layer. 134 | 135 | ![Auto Open](images/autoopen.png) 136 | 137 |
138 | 139 | 140 | 141 | :::info 142 | Because you set the `autoOpen` and `autoClose` properties on the door step (and not on the door variable), 143 | you can have different behaviour depending on the side of the door you are on. You can create doors 144 | that are opening automatically on one side, but that require manual opening on the other side. 145 | ::: 146 | 147 | ### Configuring the open/close door message 148 | 149 | You can change the message displayed to the user to open / close a door with the `openTriggerMessage` and 150 | `closeTriggerMessage` properties, on the doorstep layer. 151 | 152 | ### Limiting who can open/close the door 153 | 154 | You can decide who is allowed to operate the door based on user tags. 155 | 156 | :::info 157 | User tags is a feature of the "pro" accounts. 158 | ::: 159 | 160 | Use the `tag` property on the "door step" layer. In order to operate the door from this layer, the player 161 | will need to have the specified tag. 162 | 163 | ![Door Step Tag](images/doorsteptag.png) 164 | 165 | :::info 166 | You set the `tag` property on the door step (and not on the door variable). 167 | You can therefore put the tag variable outside (the door can be opened only by authorized people), but not on the doorstep 168 | inside (people inside can exit the room, whether they have the tag or not) 169 | ::: 170 | 171 | ### Setting a digital code access on your door 172 | 173 | You can protect your door with a digital access code. 174 | 175 | Putting a code is as simple as adding a `code` property on your door. 176 | 177 | ![Door Code Property](images/code_property.png) 178 | 179 | ![Door Code](images/digicode.png) 180 | 181 | The digital access code will be displayed at the right of the closed door. 182 | 183 | :::caution 184 | Protection provided by the "code" property is weak. Indeed, the code is stored in the map that is downloaded by the browser. 185 | Therefore, it is quite easy for an attacker to read the code. The digital access code will prevent regular users from 186 | entering your room, but won't stop someone willing to hack your room. 187 | ::: 188 | -------------------------------------------------------------------------------- /docs/features/generic-action-layers.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sidebar_position: 60 4 | 5 | --- 6 | 7 | # Action Layers 8 | 9 | :::info Important 10 | To use these action layers, you need to [import the "Scripting API Extra" script in your map](/developer/map-scripting/scripting-api-extra/#importing-the-extended-features) 11 | ::: 12 | 13 | You can define special layers that will **alter the value of a ["variable"](/developer/map-scripting/references/api-state)** 14 | when walked upon. 15 | 16 | To define such a layer, create a new "tile layer" in Tiled. 17 | 18 | In this layer, add these properties: 19 | 20 | - `bindVariable`: The name of the variable that will be altered when one enters/leaves the layer 21 | - `enterValue`: (optional) The value the variable will be set to when entering the layer 22 | - `leaveValue`: (optional) The value the variable will be set to when leaving the layer 23 | 24 | ## User interaction 25 | 26 | By default, as soon as anyone enters or leaves the layer, the value of the variable will be changed. 27 | 28 | Optionally, you can request a user interaction to trigger the change of the value. Use the `triggerMessage` property 29 | to display a message to the player. When the player presses "SPACE", the value of the variable will be changed to `enterValue`. 30 | 31 | ## Managing rights 32 | 33 | You can restrict who will trigger an action layer using user **tags**. 34 | 35 | :::info 36 | User tags is a feature of the "pro" accounts. 37 | ::: 38 | 39 | Add a `tag` property on the layer. The variable will only change value if the player walking on the layer has the specified tag. 40 | -------------------------------------------------------------------------------- /docs/features/images/areaBindings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/areaBindings.png -------------------------------------------------------------------------------- /docs/features/images/autoopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/autoopen.png -------------------------------------------------------------------------------- /docs/features/images/back_doorstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/back_doorstep.png -------------------------------------------------------------------------------- /docs/features/images/bell_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/bell_layer.png -------------------------------------------------------------------------------- /docs/features/images/bell_popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/bell_popup.png -------------------------------------------------------------------------------- /docs/features/images/bell_variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/bell_variable.png -------------------------------------------------------------------------------- /docs/features/images/close_door_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/close_door_layer.png -------------------------------------------------------------------------------- /docs/features/images/code_property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/code_property.png -------------------------------------------------------------------------------- /docs/features/images/configuration_allowed_values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_allowed_values.png -------------------------------------------------------------------------------- /docs/features/images/configuration_allowed_values_description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_allowed_values_description.png -------------------------------------------------------------------------------- /docs/features/images/configuration_description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_description.png -------------------------------------------------------------------------------- /docs/features/images/configuration_description_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_description_screenshot.png -------------------------------------------------------------------------------- /docs/features/images/configuration_label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_label.png -------------------------------------------------------------------------------- /docs/features/images/configuration_label_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_label_screenshot.png -------------------------------------------------------------------------------- /docs/features/images/configuration_layers_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_layers_tree.png -------------------------------------------------------------------------------- /docs/features/images/configuration_main_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_main_page.png -------------------------------------------------------------------------------- /docs/features/images/configuration_panel_global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_panel_global.png -------------------------------------------------------------------------------- /docs/features/images/configuration_panel_local.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_panel_local.png -------------------------------------------------------------------------------- /docs/features/images/configuration_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configuration_tag.png -------------------------------------------------------------------------------- /docs/features/images/configure_the_room_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/configure_the_room_button.png -------------------------------------------------------------------------------- /docs/features/images/digicode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/digicode.png -------------------------------------------------------------------------------- /docs/features/images/door_manual.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/door_manual.mp4 -------------------------------------------------------------------------------- /docs/features/images/door_manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/door_manual.png -------------------------------------------------------------------------------- /docs/features/images/door_variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/door_variable.png -------------------------------------------------------------------------------- /docs/features/images/doorsteptag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/doorsteptag.png -------------------------------------------------------------------------------- /docs/features/images/front_doorstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/front_doorstep.png -------------------------------------------------------------------------------- /docs/features/images/local_configuration_setup_tiled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/local_configuration_setup_tiled.png -------------------------------------------------------------------------------- /docs/features/images/open_door_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/open_door_layer.png -------------------------------------------------------------------------------- /docs/features/images/open_doors_auto.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/open_doors_auto.mov -------------------------------------------------------------------------------- /docs/features/images/open_doors_auto.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/open_doors_auto.mp4 -------------------------------------------------------------------------------- /docs/features/images/open_doors_auto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/open_doors_auto.png -------------------------------------------------------------------------------- /docs/features/images/sample_configuration_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/sample_configuration_screen.png -------------------------------------------------------------------------------- /docs/features/images/script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/script.png -------------------------------------------------------------------------------- /docs/features/images/templated_property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/templated_property.png -------------------------------------------------------------------------------- /docs/features/images/variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/variable.png -------------------------------------------------------------------------------- /docs/features/images/variables_in_configuration_layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/variables_in_configuration_layer.png -------------------------------------------------------------------------------- /docs/features/images/visible_property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/docs/features/images/visible_property.png -------------------------------------------------------------------------------- /docs/features/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | # Scripting Api Extra 5 | 6 | WorkAdventure features can be extended through the use of the [scripting API](https://docs.workadventu.re/developer/map-scripting/). 7 | 8 | This means anyone can write "scripts" that can be imported in any map and that add new properties to WorkAdventure. 9 | If you are a developer, do not hesitate to have a look at the [scripting API](https://docs.workadventu.re/developer/map-scripting/) 10 | and to create your own custom properties / features. 11 | 12 | The WorkAdventure team also provides its own scripts that add various features. We put those features 13 | in a common package we call [Scripting API Extra](https://github.com/workadventure/scripting-api-extra). 14 | 15 | In this section, you will find a list of these extended features. 16 | 17 | ## Importing the "extended features" 18 | 19 | Because a script is hosting the extended features, you need to import that script explicitly into your map. 20 | 21 | There are 3 ways to import those extended features: 22 | 23 | - importing the "Scripting API Extra" library directly in your map 24 | - or importing the "Scripting API Extra" library dynamically from your Javascript script 25 | - or bundling the "Scripting API Extra" library in your own script using NPM and a bundler like Webpack or Vite 26 | 27 | :::success 28 | If you are using the [Map Starter Kit](https://github.com/workadventure/map-starter-kit) (as recommended in the WorkAdventure documentation) the "extended features" are 29 | already imported in your map for the `src/main.ts` file of the starter kit. You can skip the following chapters. 30 | ::: 31 | 32 | ### Importing the script in the map 33 | 34 | :::warning 35 | We do not recommend this method because you will not be able to import other scripts in your map. 36 | ::: 37 | 38 | You can do so by adding a `script` property at the top level of your map, pointing to the URL: 39 | `https://cdn.jsdelivr.net/npm/@workadventure/scripting-api-extra@^1/dist/bundle.js` 40 | 41 | ![](images/script.png) 42 | Importing the "Scripting API Extra" library using the "script" property. 43 | 44 | :::info Reminder 45 | To access the map properties in Tiled, you can click on the **Map** > **Map Properties** menu. 46 | ::: 47 | 48 | ### Importing the script dynamically from your Javascript script 49 | 50 | If you already have a Javascript script in your map, you can import the scripting API as a module. 51 | 52 | :::caution 53 | This method is only recommended for simple scripts. If you are using a Javascript bundler (like Webpack) to build your Javascript 54 | script, have a look at the next section. 55 | ::: 56 | 57 | To import the "Scripting API Extra" library dynamically from your script, at the top of your JS script, add this line: 58 | 59 | ```javascript 60 | import { } from "https://cdn.jsdelivr.net/npm/@workadventure/scripting-api-extra@^1"; 61 | ``` 62 | 63 | ### Bundling the "Scripting API Extra" library in your script 64 | 65 | If you already have a script in your map and if this script is built using a bundler like Webpack or Rollup 66 | (this is the case if you are using the [WorkAdventure Map Starter Kit](https://github.com/workadventure/map-starter-kit)), 67 | you can import the "Scripting API Extra" library as a "dependency" of your script. 68 | 69 | :::info Note 70 | The WorkAdventure Map Starter Kit is already importing the Scripting API Extra library as a dependency, so you 71 | have nothing to do if you use it for your map. 72 | ::: 73 | 74 | ``` 75 | npm install --save @workadventure/scripting-api-extra 76 | ``` 77 | 78 | :::caution 79 | Here, we assume that the script you wrote is using a bundler (like Webpack) and that you already have dependencies in 80 | your project using a `package.json` file. If you are not familiar with NPM, or bundlers, we highly recommend using 81 | the [WorkAdventure Map Starter Kit](https://github.com/workadventure/map-starter-kit) that comes 82 | with sane defaults. 83 | ::: 84 | 85 | Once the "Scripting API Extra" library is imported, you still need to initialize it. This can be done by calling a 86 | single `bootstrapExtra()` method: 87 | 88 | ```typescript 89 | import { bootstrapExtra } from "@workadventure/scripting-api-extra"; 90 | 91 | // Calling bootstrapExtra will initiliaze all the "custom properties" 92 | bootstrapExtra(); 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/features/reference.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sidebar_position: 1000 4 | 5 | --- 6 | 7 | # Properties Reference 8 | 9 | :::info 10 | To use the properties in this document, you need to [import the "Scripting API Extra" script in your map](/developer/map-scripting/scripting-api-extra/#importing-the-extended-features) 11 | ::: 12 | 13 | This document does not list the properties available out-of-the-box in WorkAdventure but **only the properties added 14 | by the "Scripting API Extra" package**. 15 | 16 | ## Layer Properties 17 | 18 | Those properties can be set on layers. 19 | 20 | | Name | Type | Context | Description | 21 | |---------------------------------------------------------------------------|---------|----------------|------------------------------------------------------------------------------------------------------------------------------| 22 | | [`bellVariable`](bells#the-bell-display-layer) | string | Bell layer | Points to the name of the variable containing the bell settings | 23 | | [`bellPopup`](bells#adding-a-bell-button) | string | Bell layer | The name of a rectangle object on the object layer in the map that will display the "Ring" button to ring the bell. | 24 | | [`bellButtonText`](bells#adding-a-bell-button) | string | Bell layer | the text to display in the button to ring the bell. Defaults to "Ring" | 25 | | [`autoOpen`](doors#automatically-vs-manually-opening-the-door) | boolean | Doorstep layer | By setting `autoOpen` to true, the door will automatically open when someone walks in the door step layer. | 26 | | [`autoClose`](doors#automatically-vs-manually-opening-the-door) | boolean | Doorstep layer | By setting `autoClose` to true, the door will automatically close when someone walks out of the door step layer. | 27 | | [`openTriggerMessage`](doors#configuring-the-openclose-door-message) | string | Doorstep layer | The action message displayed to open the door | 28 | | [`closeTriggerMessage`](doors#configuring-the-openclose-door-message) | string | Doorstep layer | The action message displayed to close the door | 29 | | [`tag`](doors#limiting-who-can-openclose-the-door) | string | Doorstep layer | Limits who can operate the door from this doorstep | 30 | | [`code`](doors#setting-a-digital-code-access-on-your-door) | string | Doorstep layer | A digital access code | 31 | | [`bindVariable`](generic-action-layers) | string | Action layer | The name of the variable that will be altered when one enters/leaves the layer | 32 | | [`enterValue`](generic-action-layers) | string | Action layer | (optional) The value the variable will be set to when entering the layer | 33 | | [`leaveValue`](generic-action-layers) | string | Action layer | (optional) The value the variable will be set to when leaving the layer | 34 | | [`visible`](variable-to-property-binding#the-special-visible-property) | string | *Any layer* | This property can control the visibility of a layer. Any "truthy" value will display the layer. An empty value will hide it. | 35 | 36 | ## Variables properties 37 | 38 | Those properties can be set on variables. 39 | 40 | Name | Type | Description 41 | ------------------------|--------------------|----------------------- 42 | [`bell`](bells#the-bell-variable) | boolean (`true`) | Adding this property marks the variable as representing a bell 43 | [`bellSound`](bells#the-bell-variable) | string | URL of the sound of the bell ringing 44 | [`soundRadius`](bells#the-bell-variable) | number | The radius (in pixels) of the sound of the bell or door opening/closing 45 | [`door`](doors#the-door-variable) | boolean (`true`) | Adding this property marks the variable as representing a door 46 | [`openLayer`](doors#the-door-variable) | string | On a "door" variable, this MUST contain the name of the opened door layer (or several layers on multiple lines) 47 | [`clodeLayer`](doors#the-door-variable) | string | On a "door" variable, this MUST contain the name of the closed door layer (or several layers on multiple lines) 48 | [`openSound`](doors#opening--closing-sound) | string | URL of the sound of a door opening 49 | [`closeSound`](doors#opening--closing-sound) | string | URL of the sound of a door closing 50 | -------------------------------------------------------------------------------- /docs/features/variable-to-property-binding.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sidebar_position: 50 4 | 5 | --- 6 | 7 | # Binding Variables to Properties 8 | 9 | :::info 10 | To use variables to properties binding, you need to [import the "Scripting API Extra" script in your map](/developer/map-scripting/scripting-api-extra/#importing-the-extended-features) 11 | ::: 12 | 13 | In WorkAdventure maps, ["variables"](/developer/map-scripting/references/api-state) are used to share a state between 14 | players. 15 | 16 | Using the Scripting API Extra library, you can bind your variables values directly into properties on your map. 17 | 18 | In a property of your map, use the `{{{ variableName }}}` to refer to the name of a property. 19 | 20 | **Sample** 21 | 22 | Let's imagine you want to dynamically change the URL of a co-website based on a variable value. 23 | You can create a new variable named "myWebsiteUrl" and bind it to the `openWebsite` property of your co-website layer. 24 | 25 | ![Variables](images/variable.png) 26 | 27 | ![Templated Property](images/templated_property.png) 28 | The property referring to the variable 29 | 30 | ## Context 31 | 32 | You can use template properties in: 33 | 34 | - any property of any "tile" layer 35 | - any property of a dynamic "area" object 36 | 37 | :::caution 38 | To use bindings with area objects, you must first set the "dynamic" property to true. 39 | ::: 40 | 41 | It should be noted that "dynamic" areas are accessible via the scripting API but are not editable in the (upcoming) map editor. 42 | 43 | ![Dynamic Area](images/areaBindings.png) 44 | 45 | _The Dynamic custom property_ 46 | 47 | ## Configuration 48 | 49 | Binding variables to properties can make your map reactive to variable changes, but you still need to find a way 50 | to modify the values of variables. There are plenty of ways to do this, including: 51 | 52 | - [Using the scripting API](https://docs.workadventu.re/developer/map-scripting/references/api-state) 53 | - [Using auto-generated configuration screen](automatic-configuration) 54 | - [Using generic action layers](generic-action-layers) 55 | 56 | ## About bindings 57 | 58 | Use `{{{ variableName }}}` to refer to a variable name. 59 | 60 | Behind the scene the [Mustache templating engine](https://en.wikipedia.org/wiki/Mustache_(template_system)) is used. 61 | This means you can use all the features of Mustache like conditional: 62 | 63 | `openWebsite: {{#enableWebsite}}https://example.com{{/enableWebsite}}` 64 | 65 | The website above will be displayed only if the `enableWebsite` variable is set to `true`. 66 | 67 | 68 | 69 | :::caution 70 | Be sure to use `{{{ variableName }}}` for binding variable and NOT `{{ variableName }}`. The version with a double 71 | curly-braces will work most of the time, but it escapes HTML characters (which is not needed in properties of a map) 72 | and this might cause weird behaviours (like breaking URLs) 73 | ::: 74 | 75 | 76 | ## The special "visible" property 77 | 78 | You can control the visibility of a layer with the `visible` **custom** property. 79 | 80 | If this custom property is set, it will override the "Visible" property of the layer. 81 | 82 | If you bind it to a variable, you can display or hide a layer based on the value of a variable. 83 | 84 | ![Visible Property](images/visible_property.png) 85 | 86 | Usage of the `visible` property 87 | 88 | ### Inverting a boolean variable 89 | 90 | In the example above, the `holeInWall` property is a boolean variable. If it is set to `true`, we want to hide the 91 | layer. So we are setting a `visible` property on the layer. If we put `visible: {{{ holeInWall }}}` the layer 92 | would be visible when `holeInWall` is `true`. But here, we want the opposite: the layer must be displayed when 93 | `holeInWall` variable is `false`. To do this, we can use Mustache's "inverted sections" (delimited by `{{^variable}}...{{/variable}}`). 94 | This section will be displayed if the variable is false or empty. 95 | 96 | Therefore, `{{^holeInWall}}1{{/holeInWall}}` will return "1" when the value of `holeInWall` is false, and will be empty otherwise. 97 | -------------------------------------------------------------------------------- /jest-base.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@jest/types').Config.InitialOptions} */ 2 | module.exports = { 3 | globals: { 4 | "ts-jest": { 5 | tsconfig: "tsconfig.spec.json", 6 | }, 7 | }, 8 | transform: { 9 | "^.+\\.(ts|tsx)$": "ts-jest", 10 | }, 11 | testEnvironment: "node", 12 | }; 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./jest-base') 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | rootDir: '.', 6 | roots: ['/test'], 7 | testMatch: ['**/?(*.)+(spec|test).+(ts|tsx|js)'], 8 | testPathIgnorePatterns: ['/src/__mocks__/*'], 9 | setupFilesAfterEnv: ['./test/setup-test.js'], 10 | cacheDirectory: '/.cache/unit', 11 | collectCoverage: true, 12 | collectCoverageFrom: ['/src/**/*.ts', '!**/node_modules/**', '!**/vendor/**'], 13 | coverageDirectory: '/coverage', 14 | coverageReporters: [['lcov', { projectRoot: './' }], 'text'], 15 | /*coverageThreshold: { 16 | global: { 17 | branches: 50, 18 | functions: 80, 19 | lines: 80, 20 | statements: -35, 21 | }, 22 | },*/ 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workadventure/scripting-api-extra", 3 | "version": "1.9.6", 4 | "description": "A set of utility functions / features to improve WorkAdventure maps", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.js", 7 | "scripts": { 8 | "start": "run-p serve svelte-check-watch", 9 | "build": "npm-run-all build-ts build-webpack", 10 | "build-ts": "./node_modules/typescript/bin/tsc -p ./tsconfig.build.json", 11 | "build-webpack": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", 12 | "lint": "eslint . --ext .js,.ts", 13 | "lint:fix": "npm run lint -- --fix", 14 | "test": "npm run lint && jest ./test/*", 15 | "dev": "ts-node src/index.ts", 16 | "semantic-release": "semantic-release", 17 | "semantic-release-dry-run": "semantic-release -d", 18 | "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", 19 | "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch", 20 | "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/workadventure/scripting-api-extra.git" 25 | }, 26 | "keywords": [ 27 | "typescript", 28 | "workadventure", 29 | "maps" 30 | ], 31 | "author": "David Négrier ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/workadventure/scripting-api-extra/issues" 35 | }, 36 | "homepage": "https://github.com/workadventure/scripting-api-extra#readme", 37 | "husky": { 38 | "hooks": { 39 | "pre-commit": "lint-staged", 40 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 41 | "pre-push": "npm run lint" 42 | } 43 | }, 44 | "lint-staged": { 45 | "*.ts": [ 46 | "npm run lint" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@16bits/nes.css": "^2.3.2", 51 | "@commitlint/cli": "^13.1.0", 52 | "@commitlint/config-conventional": "^13.1.0", 53 | "@rpidanny/eslint-config-typescript": "^1.0.0", 54 | "@semantic-release/changelog": "^6.0.3", 55 | "@semantic-release/commit-analyzer": "^10.0.1", 56 | "@semantic-release/git": "^10.0.1", 57 | "@semantic-release/npm": "^10.0.4", 58 | "@tailwindcss/forms": "^0.5.3", 59 | "@tsconfig/svelte": "^2.0.1", 60 | "@types/copy-webpack-plugin": "^8.0.1", 61 | "@types/jest": "^27.0.1", 62 | "@types/mini-css-extract-plugin": "^2.2.0", 63 | "@types/mustache": "^4.1.2", 64 | "@types/webpack-dev-server": "^4.1.0", 65 | "@workadventure/iframe-api-typings": "^1.14.3", 66 | "autoprefixer": "^10.4.8", 67 | "copy-webpack-plugin": "^9.0.1", 68 | "cross-env": "^7.0.3", 69 | "css-loader": "^5.2.4", 70 | "eslint": "^7.6.0", 71 | "fork-ts-checker-webpack-plugin": "^6.2.9", 72 | "html-webpack-plugin": "^5.3.1", 73 | "husky": "^7.0.1", 74 | "jest": "^26.2.2", 75 | "lint-staged": "^11.0.0", 76 | "mini-css-extract-plugin": "^2.2.2", 77 | "node-polyfill-webpack-plugin": "^1.1.2", 78 | "npm-run-all": "^4.1.5", 79 | "postcss": "^8.4.16", 80 | "prettier": "^2.0.5", 81 | "sass": "^1.32.12", 82 | "sass-loader": "^12.1.0", 83 | "semantic-release": "^21.0.7", 84 | "svelte": "^3.38.2", 85 | "svelte-check": "^2.1.0", 86 | "svelte-loader": "^3.1.1", 87 | "svelte-preprocess": "^4.7.3", 88 | "tailwindcss": "^3.1.8", 89 | "ts-jest": "^26.1.4", 90 | "ts-loader": "^9.2.5", 91 | "ts-node": "^10.1.0", 92 | "tsc": "^2.0.3", 93 | "typescript": "^4.0.2", 94 | "webpack": "^5.31.2", 95 | "webpack-cli": "^4.6.0", 96 | "webpack-dev-server": "^4.1.0", 97 | "webpack-merge": "^5.7.3" 98 | }, 99 | "files": [ 100 | "dist/**/*" 101 | ], 102 | "publishConfig": { 103 | "access": "public" 104 | }, 105 | "unpkg": "dist/bundle.js", 106 | "dependencies": { 107 | "@fontsource/press-start-2p": "^4.5.10", 108 | "@workadventure-style/sweetsky": "^1.0.2", 109 | "@workadventure/tiled-map-type-guard": "^2.0.4", 110 | "i18next": "^23.11.2", 111 | "mustache": "^4.2.0", 112 | "play-dtmf": "^0.1.1", 113 | "postcss-loader": "^7.0.1" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const postcssConfig = require("@workadventure-style/sweetsky/postcss.config"); 2 | module.exports = postcssConfig; 3 | -------------------------------------------------------------------------------- /resources/images/camera.svg: -------------------------------------------------------------------------------- 1 | 2 | Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj 3 | 4 | -------------------------------------------------------------------------------- /resources/images/keys.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/keys.gif -------------------------------------------------------------------------------- /resources/images/logo-message-pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/logo-message-pixel.png -------------------------------------------------------------------------------- /resources/images/micro.svg: -------------------------------------------------------------------------------- 1 | 2 | Made with Pixels to Svg https://codepen.io/shshaw/pen/XbxvNj 3 | 4 | -------------------------------------------------------------------------------- /resources/images/mouse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/mouse.gif -------------------------------------------------------------------------------- /resources/images/tutov1/discover/GlobalMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/discover/GlobalMessage.png -------------------------------------------------------------------------------- /resources/images/tutov1/discover/Object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/discover/Object.png -------------------------------------------------------------------------------- /resources/images/tutov1/discover/Silent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/discover/Silent.png -------------------------------------------------------------------------------- /resources/images/tutov1/interact/Bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/interact/Bubble.png -------------------------------------------------------------------------------- /resources/images/tutov1/interact/Lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/interact/Lock.png -------------------------------------------------------------------------------- /resources/images/tutov1/interact/ShareScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/interact/ShareScreen.png -------------------------------------------------------------------------------- /resources/images/tutov1/move/CamOFF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/move/CamOFF.png -------------------------------------------------------------------------------- /resources/images/tutov1/move/ZQSD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/move/ZQSD.png -------------------------------------------------------------------------------- /resources/images/tutov1/move/wokamove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/move/wokamove.png -------------------------------------------------------------------------------- /resources/images/tutov1/step1-onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step1-onboarding.png -------------------------------------------------------------------------------- /resources/images/tutov1/step1/keyboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/images/tutov1/step1/mouse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/images/tutov1/step1/right-click.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/images/tutov1/step1/touch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/images/tutov1/step2-onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step2-onboarding.png -------------------------------------------------------------------------------- /resources/images/tutov1/step2/woka-meet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step2/woka-meet.png -------------------------------------------------------------------------------- /resources/images/tutov1/step3-onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step3-onboarding.png -------------------------------------------------------------------------------- /resources/images/tutov1/step3/woka-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step3/woka-action.png -------------------------------------------------------------------------------- /resources/images/tutov1/step4-onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step4-onboarding.png -------------------------------------------------------------------------------- /resources/images/tutov1/step5-onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step5-onboarding.png -------------------------------------------------------------------------------- /resources/images/tutov1/step6-onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/step6-onboarding.png -------------------------------------------------------------------------------- /resources/images/tutov1/welcome/Move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/welcome/Move.png -------------------------------------------------------------------------------- /resources/images/tutov1/welcome/Talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/welcome/Talk.png -------------------------------------------------------------------------------- /resources/images/tutov1/welcome/laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/images/tutov1/welcome/laptop.png -------------------------------------------------------------------------------- /resources/sounds/LICENSE.txt: -------------------------------------------------------------------------------- 1 | door-bell-1.mp3, knocking-on-door.mp3, opening-door-1.mp3 and closing-door-1.mp3 are derived work from free sound effects from https://www.fesliyanstudios.com 2 | 3 | See https://www.fesliyanstudios.com/sound-effects-policy 4 | -------------------------------------------------------------------------------- /resources/sounds/closing-door-1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/sounds/closing-door-1.mp3 -------------------------------------------------------------------------------- /resources/sounds/door-bell-1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/sounds/door-bell-1.mp3 -------------------------------------------------------------------------------- /resources/sounds/knocking-on-door.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/sounds/knocking-on-door.mp3 -------------------------------------------------------------------------------- /resources/sounds/opening-door-1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/sounds/opening-door-1.mp3 -------------------------------------------------------------------------------- /resources/videos/firstConnection/bubble-zone-slower.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/videos/firstConnection/bubble-zone-slower.mp4 -------------------------------------------------------------------------------- /resources/videos/firstConnection/joystick-mobile.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/videos/firstConnection/joystick-mobile.mp4 -------------------------------------------------------------------------------- /resources/videos/firstConnection/webcam-on-off.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/resources/videos/firstConnection/webcam-on-off.m4v -------------------------------------------------------------------------------- /src/AreaObject.ts: -------------------------------------------------------------------------------- 1 | import type { ITiledMapObject } from "@workadventure/tiled-map-type-guard/dist/ITiledMapObject"; 2 | import { getLayersMap } from "./LayersFlattener"; 3 | 4 | export async function getAreaObject(): Promise { 5 | const layers = await getLayersMap(); 6 | const areaArray: Array = []; 7 | for (const layer of layers.values()) { 8 | if (layer.type === "objectgroup") { 9 | for (const object of layer.objects) { 10 | if (object.type === "area" || object.class === "area") { 11 | areaArray.push(object); 12 | } 13 | } 14 | } 15 | } 16 | return areaArray; 17 | } 18 | -------------------------------------------------------------------------------- /src/Features/configuration.ts: -------------------------------------------------------------------------------- 1 | import { Properties } from "../Properties"; 2 | import type { ITiledMap, ITiledMapLayer } from "@workadventure/tiled-map-type-guard/dist"; 3 | import { defaultAssetsUrl } from "./default_assets_url"; 4 | import { getLayersMap } from "../LayersFlattener"; 5 | import { openConfig } from "../VariablesExtra"; 6 | import type { ActionMessage } from "@workadventure/iframe-api-typings"; 7 | 8 | let layersMap!: Map; 9 | 10 | /** 11 | * Initialize the configuration button in the menu 12 | */ 13 | export async function initConfiguration(assetsUrl?: string | undefined): Promise { 14 | const map: ITiledMap = (await WA.room.getTiledMap()) as ITiledMap; 15 | assetsUrl = assetsUrl ?? defaultAssetsUrl; 16 | layersMap = await getLayersMap(); 17 | const configurationLayer = map.layers.find((layer) => layer.name === "configuration"); 18 | 19 | if (configurationLayer) { 20 | // Controls the configuration panel triggered from the menu 21 | const properties = new Properties(configurationLayer.properties); 22 | const tag = properties.getString("tag"); 23 | if (!tag || WA.player.tags.includes(tag)) { 24 | WA.ui.registerMenuCommand("Configure the room", () => { 25 | WA.nav.openCoWebSite(assetsUrl + "/configuration.html", true); 26 | }); 27 | } 28 | 29 | // Controls the configuration panel triggered from a zone 30 | for (const layer of layersMap.values()) { 31 | const properties = new Properties(layer.properties); 32 | const openConfigVariables = properties.getString("openConfig"); 33 | if (openConfigVariables && layer.type === "tilelayer") { 34 | initLocalConfigurationPanel(openConfigVariables.split(","), layer.name, properties); 35 | } 36 | } 37 | } 38 | } 39 | 40 | function initLocalConfigurationPanel( 41 | openConfigVariables: string[], 42 | layerName: string, 43 | properties: Properties, 44 | ): void { 45 | let actionMessage: ActionMessage | undefined = undefined; 46 | 47 | const tag = properties.getString("openConfigAdminTag"); 48 | let allowedByTag = true; 49 | if (tag && !WA.player.tags.includes(tag)) { 50 | allowedByTag = false; 51 | } 52 | 53 | function displayConfigurationMessage(): void { 54 | if (actionMessage) { 55 | actionMessage.remove(); 56 | } 57 | actionMessage = WA.ui.displayActionMessage({ 58 | message: 59 | properties.getString("openConfigTriggerMessage") ?? 60 | "Press SPACE or touch here to configure", 61 | callback: () => openConfig(openConfigVariables), 62 | }); 63 | } 64 | 65 | function closeConfigurationPanel(): void { 66 | WA.nav.closeCoWebSite(); 67 | } 68 | 69 | WA.room.onEnterLayer(layerName).subscribe(() => { 70 | const openConfigTriggerValue = properties.getString("openConfigTrigger"); 71 | 72 | // Do not display conf panel if the user is not allowed by tag 73 | if (allowedByTag) { 74 | if (openConfigTriggerValue && openConfigTriggerValue === "onaction") { 75 | displayConfigurationMessage(); 76 | } else { 77 | openConfig(openConfigVariables); 78 | } 79 | } 80 | }); 81 | 82 | WA.room.onLeaveLayer(layerName).subscribe(() => { 83 | if (actionMessage) { 84 | actionMessage.remove(); 85 | closeConfigurationPanel(); 86 | } else { 87 | closeConfigurationPanel(); 88 | } 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /src/Features/default_assets_url.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The base URL for the default assets. 3 | * This file is updated dynamically during the package build process. 4 | */ 5 | export const defaultAssetsUrl = ""; 6 | -------------------------------------------------------------------------------- /src/Features/doors.ts: -------------------------------------------------------------------------------- 1 | import { getVariables, VariableDescriptor } from "../VariablesExtra"; 2 | import { getLayersMap } from "../LayersFlattener"; 3 | import { Properties } from "../Properties"; 4 | import { findLayersBoundaries } from "../LayersExtra"; 5 | import type { ITiledMapLayer, ITiledMapObject } from "@workadventure/tiled-map-type-guard/dist"; 6 | import type { ITiledMapTileLayer } from "@workadventure/tiled-map-type-guard/dist/ITiledMapTileLayer"; 7 | import type { Popup, ActionMessage, EmbeddedWebsite } from "@workadventure/iframe-api-typings"; 8 | import { defaultAssetsUrl } from "./default_assets_url"; 9 | import { workadventureAssetsHtmlUrl } from "./workadventure_assets_url"; 10 | import { getAreaObject } from "../AreaObject"; 11 | 12 | let layersMap!: Map; 13 | let playerX = 0; 14 | let playerY = 0; 15 | 16 | /** 17 | * Updates the layers representing the door to match the state of the variable. 18 | */ 19 | function updateDoorLayers(variable: VariableDescriptor): void { 20 | if (WA.state[variable.name]) { 21 | let layers = variable.properties.mustGetString("openLayer"); 22 | for (const layer of layers.split("\n")) { 23 | WA.room.showLayer(layer); 24 | } 25 | 26 | layers = variable.properties.mustGetString("closeLayer"); 27 | for (const layer of layers.split("\n")) { 28 | WA.room.hideLayer(layer); 29 | } 30 | } else { 31 | let layers = variable.properties.mustGetString("openLayer"); 32 | for (const layer of layers.split("\n")) { 33 | WA.room.hideLayer(layer); 34 | } 35 | 36 | layers = variable.properties.mustGetString("closeLayer"); 37 | for (const layer of layers.split("\n")) { 38 | WA.room.showLayer(layer); 39 | } 40 | } 41 | } 42 | 43 | function playOpenSound(variable: VariableDescriptor): void { 44 | const url = variable.properties.getString("openSound"); 45 | const radius = variable.properties.getNumber("soundRadius"); 46 | let volume = 1; 47 | if (radius) { 48 | const distance = getDistance(variable.properties.mustGetString("openLayer").split("\n")); 49 | if (distance > radius) { 50 | return; 51 | } 52 | volume = 1 - distance / radius; 53 | } 54 | 55 | if (url) { 56 | WA.sound.loadSound(url).play({ 57 | volume, 58 | }); 59 | } 60 | } 61 | 62 | function playCloseSound(variable: VariableDescriptor): void { 63 | const url = variable.properties.getString("closeSound"); 64 | const radius = variable.properties.getNumber("soundRadius"); 65 | let volume = 1; 66 | if (radius) { 67 | const distance = getDistance(variable.properties.mustGetString("closeLayer").split("\n")); 68 | if (distance > radius) { 69 | return; 70 | } 71 | volume = 1 - distance / radius; 72 | } 73 | 74 | if (url) { 75 | WA.sound.loadSound(url).play({ 76 | volume, 77 | }); 78 | } 79 | } 80 | 81 | function getTileLayers(layerNames: string[]): ITiledMapTileLayer[] { 82 | return layerNames 83 | .map((layerName) => layersMap.get(layerName)) 84 | .filter((layer) => layer?.type === "tilelayer") as ITiledMapTileLayer[]; 85 | } 86 | 87 | /** 88 | * Get the distance between the player and the center of the layer passed in parameter. 89 | */ 90 | function getDistance(layerNames: string[]): number { 91 | const layers = getTileLayers(layerNames); 92 | const boundaries = findLayersBoundaries(layers); 93 | const xLayer = ((boundaries.right - boundaries.left) / 2 + boundaries.left) * 32; 94 | const yLayer = ((boundaries.bottom - boundaries.top) / 2 + boundaries.top) * 32; 95 | 96 | return Math.sqrt(Math.pow(playerX - xLayer, 2) + Math.pow(playerY - yLayer, 2)); 97 | } 98 | 99 | function initDoor(variable: VariableDescriptor): void { 100 | WA.state.onVariableChange(variable.name).subscribe(() => { 101 | if (WA.state[variable.name]) { 102 | playOpenSound(variable); 103 | } else { 104 | playCloseSound(variable); 105 | } 106 | 107 | updateDoorLayers(variable); 108 | }); 109 | updateDoorLayers(variable); 110 | } 111 | 112 | function initDoorstep( 113 | doorstepZone: ITiledMapTileLayer | ITiledMapObject, 114 | doorVariable: VariableDescriptor, 115 | properties: Properties, 116 | assetsUrl: string, 117 | ): void { 118 | const name = doorstepZone.name; 119 | let actionMessage: ActionMessage | undefined = undefined; 120 | let keypadWebsite: EmbeddedWebsite | undefined = undefined; 121 | let inZone = false; 122 | 123 | const tag = properties.getString("tag"); 124 | let allowed = true; 125 | if (tag && !WA.player.tags.includes(tag)) { 126 | allowed = false; 127 | } 128 | const accessRestricted = !!tag; 129 | 130 | function displayCloseDoorMessage(): void { 131 | if (actionMessage) { 132 | actionMessage.remove(); 133 | } 134 | 135 | actionMessage = WA.ui.displayActionMessage({ 136 | message: properties.getString("closeTriggerMessage") ?? "Press SPACE to close the door", 137 | callback: () => { 138 | WA.state[doorVariable.name] = false; 139 | displayOpenDoorMessage(); 140 | }, 141 | }); 142 | } 143 | 144 | function displayOpenDoorMessage(): void { 145 | if (actionMessage) { 146 | actionMessage.remove(); 147 | } 148 | actionMessage = WA.ui.displayActionMessage({ 149 | message: properties.getString("openTriggerMessage") ?? "Press SPACE to open the door", 150 | callback: () => { 151 | WA.state[doorVariable.name] = true; 152 | displayCloseDoorMessage(); 153 | }, 154 | }); 155 | } 156 | 157 | function openKeypad(): void { 158 | let boundaries: { 159 | top: number; 160 | left: number; 161 | right: number; 162 | bottom: number; 163 | }; 164 | if (doorstepZone.type === "tilelayer") { 165 | boundaries = findLayersBoundaries( 166 | getTileLayers(doorVariable.properties.mustGetString("closeLayer").split("\n")), 167 | ); 168 | } else { 169 | if ( 170 | doorstepZone.x === undefined || 171 | doorstepZone.y === undefined || 172 | doorstepZone.width === undefined || 173 | doorstepZone.height === undefined 174 | ) { 175 | throw new Error( 176 | `Doorstep zone "${doorstepZone.name}" is missing x, y, width or height`, 177 | ); 178 | } 179 | boundaries = { 180 | top: doorstepZone.y, 181 | left: doorstepZone.x, 182 | right: doorstepZone.x + doorstepZone.width, 183 | bottom: doorstepZone.y + doorstepZone.height, 184 | }; 185 | } 186 | 187 | keypadWebsite = WA.room.website.create({ 188 | name: "doorKeypad" + name, 189 | url: assetsUrl + "/keypad.html#" + encodeURIComponent(name), 190 | position: { 191 | x: boundaries.right * 32, 192 | y: boundaries.top * 32, 193 | width: 32 * 3, 194 | height: 32 * 4, 195 | }, 196 | allowApi: true, 197 | }); 198 | } 199 | 200 | function closeKeypad(): void { 201 | if (keypadWebsite) { 202 | WA.room.website.delete(keypadWebsite.name); 203 | keypadWebsite = undefined; 204 | } 205 | } 206 | 207 | function onEnter() { 208 | inZone = true; 209 | if (properties.getBoolean("autoOpen") && allowed) { 210 | WA.state[doorVariable.name] = true; 211 | return; 212 | } 213 | 214 | if ( 215 | !WA.state[doorVariable.name] && 216 | ((accessRestricted && !allowed) || !accessRestricted) && // Do not display code if user is allowed by tag 217 | (properties.getString("code") || properties.getString("codeVariable")) 218 | ) { 219 | openKeypad(); 220 | return; 221 | } 222 | 223 | if (!allowed) { 224 | return; 225 | } 226 | 227 | if (WA.state[doorVariable.name]) { 228 | displayCloseDoorMessage(); 229 | } else { 230 | displayOpenDoorMessage(); 231 | } 232 | } 233 | 234 | function onLeave() { 235 | inZone = false; 236 | if (properties.getBoolean("autoClose")) { 237 | WA.state[doorVariable.name] = false; 238 | } 239 | 240 | if (actionMessage) { 241 | actionMessage.remove(); 242 | } 243 | closeKeypad(); 244 | } 245 | 246 | if (doorstepZone.type === "tilelayer") { 247 | WA.room.onEnterLayer(name).subscribe(onEnter); 248 | WA.room.onLeaveLayer(name).subscribe(onLeave); 249 | } else { 250 | WA.room.area.onEnter(name).subscribe(onEnter); 251 | WA.room.area.onLeave(name).subscribe(onLeave); 252 | } 253 | 254 | WA.state.onVariableChange(doorVariable.name).subscribe(() => { 255 | if (inZone) { 256 | if (!properties.getBoolean("autoClose") && WA.state[doorVariable.name] === true) { 257 | displayCloseDoorMessage(); 258 | } 259 | 260 | if (keypadWebsite && WA.state[doorVariable.name] === true) { 261 | closeKeypad(); 262 | } 263 | 264 | if (!properties.getBoolean("autoOpen") && WA.state[doorVariable.name] === false) { 265 | displayOpenDoorMessage(); 266 | } 267 | } 268 | }); 269 | } 270 | 271 | function playBellSound(variable: VariableDescriptor): void { 272 | const url = variable.properties.mustGetString("bellSound"); 273 | const radius = variable.properties.getNumber("soundRadius"); 274 | let volume = 1; 275 | if (radius) { 276 | const distance = Math.sqrt( 277 | Math.pow(variable.x - playerX, 2) + Math.pow(variable.y - playerY, 2), 278 | ); 279 | if (distance > radius) { 280 | return; 281 | } 282 | volume = 1 - distance / radius; 283 | } 284 | 285 | WA.sound.loadSound(url).play({ 286 | volume, 287 | }); 288 | } 289 | 290 | function initBell(variable: VariableDescriptor): void { 291 | if (WA.state[variable.name] === undefined) { 292 | WA.state[variable.name] = 0; 293 | } 294 | 295 | WA.state.onVariableChange(variable.name).subscribe(() => { 296 | if (WA.state[variable.name]) { 297 | playBellSound(variable); 298 | } 299 | }); 300 | } 301 | 302 | function initBellLayer( 303 | bellVariable: string, 304 | properties: Properties, 305 | bellZone: ITiledMapTileLayer | ITiledMapObject, 306 | ): void { 307 | let popup: Popup | undefined = undefined; 308 | 309 | const bellPopupName = properties.getString("bellPopup"); 310 | 311 | if (bellZone.type === "tilelayer") { 312 | const layerName = bellZone.name; 313 | WA.room.onEnterLayer(layerName).subscribe(() => { 314 | if (!bellPopupName) { 315 | WA.state[bellVariable] = (WA.state[bellVariable] as number) + 1; 316 | } else { 317 | popup = WA.ui.openPopup(bellPopupName, "", [ 318 | { 319 | label: properties.getString("bellButtonText") ?? "Ring", 320 | callback: () => { 321 | WA.state[bellVariable] = (WA.state[bellVariable] as number) + 1; 322 | }, 323 | }, 324 | ]); 325 | } 326 | }); 327 | 328 | WA.room.onLeaveLayer(layerName).subscribe(() => { 329 | if (popup) { 330 | popup.close(); 331 | popup = undefined; 332 | } 333 | }); 334 | } else { 335 | const objectName = bellZone.name; 336 | WA.room.area.onEnter(objectName).subscribe(() => { 337 | if (!bellPopupName) { 338 | WA.state[bellVariable] = (WA.state[bellVariable] as number) + 1; 339 | } else { 340 | popup = WA.ui.openPopup(bellPopupName, "", [ 341 | { 342 | label: properties.getString("bellButtonText") ?? "Ring", 343 | callback: () => { 344 | WA.state[bellVariable] = (WA.state[bellVariable] as number) + 1; 345 | }, 346 | }, 347 | ]); 348 | } 349 | }); 350 | 351 | WA.room.area.onLeave(objectName).subscribe(() => { 352 | if (popup) { 353 | popup.close(); 354 | popup = undefined; 355 | } 356 | }); 357 | } 358 | } 359 | 360 | /** 361 | * Initialize doors and bells parsing on the map. 362 | * 363 | * assetsUrl is the URL to the assets directory containing the compiled "keypad.html" file (for digit code) 364 | */ 365 | export async function initDoors(assetsUrl?: string | undefined): Promise { 366 | assetsUrl = assetsUrl ?? (workadventureAssetsHtmlUrl || defaultAssetsUrl); 367 | const variables = await getVariables(); 368 | layersMap = await getLayersMap(); 369 | 370 | for (const variable of variables.values()) { 371 | if (variable.properties.get("door")) { 372 | initDoor(variable); 373 | } 374 | 375 | if (variable.properties.get("bell")) { 376 | initBell(variable); 377 | } 378 | } 379 | 380 | for (const layer of layersMap.values()) { 381 | const properties = new Properties(layer.properties); 382 | const doorVariableName = properties.getString("doorVariable"); 383 | if (doorVariableName && layer.type === "tilelayer") { 384 | const doorVariable = variables.get(doorVariableName); 385 | if (doorVariable === undefined) { 386 | throw new Error( 387 | 'Cannot find variable "' + 388 | doorVariableName + 389 | '" referred in the "doorVariable" property of layer "' + 390 | layer.name + 391 | '"', 392 | ); 393 | } 394 | initDoorstep(layer, doorVariable, properties, assetsUrl); 395 | } 396 | const bellVariable = properties.getString("bellVariable"); 397 | if (bellVariable && layer.type === "tilelayer") { 398 | initBellLayer(bellVariable, properties, layer); 399 | } 400 | } 401 | 402 | for (const object of await getAreaObject()) { 403 | const properties = new Properties(object.properties); 404 | const doorVariableName = properties.getString("doorVariable"); 405 | if (doorVariableName) { 406 | const doorVariable = variables.get(doorVariableName); 407 | if (doorVariable === undefined) { 408 | throw new Error( 409 | 'Cannot find variable "' + 410 | doorVariableName + 411 | '" referred in the "doorVariable" property of object "' + 412 | object.name + 413 | '"', 414 | ); 415 | } 416 | initDoorstep(object, doorVariable, properties, assetsUrl); 417 | } 418 | const bellVariable = properties.getString("bellVariable"); 419 | if (bellVariable) { 420 | initBellLayer(bellVariable, properties, object); 421 | } 422 | } 423 | 424 | WA.player.onPlayerMove((moveEvent) => { 425 | playerX = moveEvent.x; 426 | playerY = moveEvent.y; 427 | }); 428 | } 429 | -------------------------------------------------------------------------------- /src/Features/properties_templates.ts: -------------------------------------------------------------------------------- 1 | import { getLayersMap } from "../LayersFlattener"; 2 | import { TemplateValue } from "../TemplateValue"; 3 | import { getAreaObject } from "../AreaObject"; 4 | 5 | export async function initPropertiesTemplatesArea(): Promise { 6 | const areas = await getAreaObject(); 7 | for (const area of areas) { 8 | const properties = area.properties ?? []; 9 | for (const property of properties) { 10 | if ( 11 | property.type === "int" || 12 | property.type === "bool" || 13 | property.type === "object" || 14 | typeof property.value !== "string" 15 | ) { 16 | continue; 17 | } 18 | const template = new TemplateValue(property.value, WA.state); 19 | if (template.isPureString()) { 20 | continue; 21 | } 22 | const newValue = template.getValue(); 23 | await setPropertyArea(area.name, property.name, newValue); 24 | 25 | template.onChange(async (newValue) => { 26 | await setPropertyArea(area.name, property.name, newValue); 27 | }); 28 | } 29 | } 30 | } 31 | 32 | export async function initPropertiesTemplates(): Promise { 33 | const layers = await getLayersMap(); 34 | 35 | for (const [layerName, layer] of layers.entries()) { 36 | if (layer.type !== "objectgroup") { 37 | const properties = layer.properties ?? []; 38 | for (const property of properties) { 39 | if ( 40 | property.type === "int" || 41 | property.type === "bool" || 42 | property.type === "object" || 43 | typeof property.value !== "string" 44 | ) { 45 | continue; 46 | } 47 | const template = new TemplateValue(property.value, WA.state); 48 | if (template.isPureString()) { 49 | continue; 50 | } 51 | const newValue = template.getValue(); 52 | setProperty(layerName, property.name, newValue); 53 | 54 | template.onChange((newValue) => { 55 | setProperty(layerName, property.name, newValue); 56 | }); 57 | } 58 | } 59 | } 60 | } 61 | 62 | async function setPropertyArea( 63 | areaName: string, 64 | propertyName: string, 65 | value: string, 66 | ): Promise { 67 | console.log(areaName); 68 | const area = await WA.room.area.get(areaName); 69 | area.setProperty(propertyName, value); 70 | } 71 | /** 72 | * Sets the property value on the map. 73 | * Furthermore, if the property name is "visible", modify the visibility of the layer. 74 | */ 75 | function setProperty(layerName: string, propertyName: string, value: string): void { 76 | WA.room.setProperty(layerName, propertyName, value); 77 | if (propertyName === "visible") { 78 | if (value) { 79 | WA.room.showLayer(layerName); 80 | } else { 81 | WA.room.hideLayer(layerName); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Features/special_properties.ts: -------------------------------------------------------------------------------- 1 | import { getLayersMap } from "../LayersFlattener"; 2 | import { Properties } from "../Properties"; 3 | import { initVariableActionLayer } from "./variable_actions"; 4 | 5 | export async function initSpecialProperties(): Promise { 6 | const layers = await getLayersMap(); 7 | 8 | for (const layer of layers.values()) { 9 | const properties = new Properties(layer.properties); 10 | 11 | initVariableActionLayer(properties, layer.name); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Features/tutorialv1.ts: -------------------------------------------------------------------------------- 1 | import { defaultAssetsUrl } from "./default_assets_url"; 2 | 3 | export function launchTutorialv1(): void { 4 | const tutoUrl = `${defaultAssetsUrl}/tutorialv1.html`; 5 | console.info("Start onboarding application!", tutoUrl); 6 | 7 | console.info("Player tutorial done information: ", WA.player.state.tutorialDone); 8 | if (WA.player.state.tutorialDone) return; 9 | 10 | //open modal and show onboarding tuto 11 | WA.ui.modal.openModal({ 12 | title: "Welcome onboard!", 13 | src: tutoUrl, 14 | allow: "fullscreen; clipboard-read; clipboard-write", 15 | allowApi: true, 16 | position: "right", 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/Features/variable_actions.ts: -------------------------------------------------------------------------------- 1 | import type { ActionMessage } from "@workadventure/iframe-api-typings"; 2 | import type { Properties } from "../Properties"; 3 | 4 | export function initVariableActionLayer(properties: Properties, layerName: string): void { 5 | const variableName = properties.getString("bindVariable"); 6 | if (variableName) { 7 | const enterValue = properties.get("enterValue"); 8 | const leaveValue = properties.get("leaveValue"); 9 | const triggerMessage = properties.getString("triggerMessage"); 10 | const tag = properties.getString("tag"); 11 | 12 | setupVariableActionLayer( 13 | variableName, 14 | layerName, 15 | enterValue, 16 | leaveValue, 17 | triggerMessage, 18 | tag, 19 | ); 20 | } 21 | } 22 | 23 | let actionMessagePopup: ActionMessage | undefined; 24 | 25 | function setupVariableActionLayer( 26 | variableName: string, 27 | layerName: string, 28 | enterValue: unknown, 29 | leaveValue: unknown, 30 | triggerMessage: string | undefined, 31 | tag: string | undefined, 32 | ): void { 33 | if (tag && !WA.player.tags.includes(tag)) { 34 | return; 35 | } 36 | 37 | if (enterValue !== undefined) { 38 | WA.room.onEnterLayer(layerName).subscribe(() => { 39 | if (triggerMessage) { 40 | actionMessagePopup = WA.ui.displayActionMessage({ 41 | type: "message", 42 | message: triggerMessage, 43 | callback: () => { 44 | WA.state[variableName] = enterValue; 45 | }, 46 | }); 47 | } else { 48 | WA.state[variableName] = enterValue; 49 | } 50 | }); 51 | } 52 | if (leaveValue !== undefined) { 53 | WA.room.onLeaveLayer(layerName).subscribe(() => { 54 | WA.state[variableName] = leaveValue; 55 | if (actionMessagePopup) { 56 | actionMessagePopup.remove().catch((e) => { 57 | console.error(e); 58 | }); 59 | actionMessagePopup = undefined; 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Features/workadventure_assets_url.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The base URL for the workadventure assets. 3 | * This file is updated dynamically during the package build process. 4 | */ 5 | export const workadventureAssetsHtmlUrl = ""; 6 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Components/App.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 |

Configure the room

22 | 23 | {#if loading || $loadingConfigurationLayerStore} 24 |
25 |

Welcome to world configuration!

26 | {#if loadingVideo} 27 |
29 |
30 | {/if} 31 | 40 | 45 |
46 | {:else} 47 | {#if $configurationLayerStore} 48 | {#if $configurationLayerStore.type === 'objectgroup' || hasFilteredVariables} 49 |
50 | {:else if $configurationLayerStore.type === 'group'} 51 | 52 | {:else} 53 |
Unsupported configuration layer type
54 | {/if} 55 | {/if} 56 | {/if} 57 |
58 | 59 | 73 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Components/Field.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 | {#if description } 32 |

{ description }

33 | {/if} 34 |
35 | {#if type === 'checkbox' } 36 |
37 | isLoadingVariableStore.set(true)} 40 | on:change={() => isLoadingVariableStore.set(true)} 41 | bind:checked={ $boolVariableStore } 42 | disabled={!variable.isWritable} 43 | /> 44 | 45 |
46 | {:else if type === 'select' } 47 |
48 | 49 | 60 |
61 | {:else if type === 'radio' } 62 |
63 | 64 |
65 | {#each Object.entries(getAllowedValues()) as [name, value] } 66 |
67 | isLoadingVariableStore.set(true)} 70 | on:change={() => isLoadingVariableStore.set(true)} 71 | bind:group={$stringVariableStore} 72 | name={variable.name} 73 | value={ value } 74 | disabled={!variable.isWritable} 75 | /> 76 | 77 |
78 | {/each} 79 |
80 |
81 | {:else} 82 |
83 | 84 | isLoadingVariableStore.set(true)} 89 | on:blur={onChange} 90 | on:change={onChange} 91 | disabled={!variable.isWritable} 92 | /> 93 |
94 | {/if} 95 | {#if $isLoadingVariableStore} 96 |
97 | {:else} 98 |
99 | 100 | 101 | 102 |
103 | {/if} 104 | 105 |
106 |
107 | 108 | 113 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Components/Section.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if $currentPage !== 'configuration' } 19 |
20 | 21 |
22 | {/if} 23 | 24 |
25 | {#if $variablesStore} 26 |
27 | {#if properties.get("name") } 28 |

{ properties.get("name") }

29 | {/if} 30 | {#each [...$variablesStore.values()] as variable } 31 | {#if variable.isReadable} 32 | 33 | {/if} 34 | {/each} 35 |
36 |
37 | 42 |
43 | {/if} 44 |
45 | 46 | 54 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Components/Sections.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | {#each layers as layer} 13 | 16 | {/each} 17 |
18 | 19 | {#if $currentPage !== 'configuration' } 20 | 21 | {/if} 22 | 23 | 42 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Stores/LayersStore.ts: -------------------------------------------------------------------------------- 1 | import { derived, Readable, writable } from "svelte/store"; 2 | import type { ITiledMapLayer } from "@workadventure/tiled-map-type-guard/dist"; 3 | import { currentPage } from "./NavigationStore"; 4 | 5 | // TODO: add a store for all layers flattened 6 | // and call getTiledMap() only once onInit to set the map layers 7 | 8 | export const loadingConfigurationLayerStore = writable(false); 9 | export const configurationLayerStore = derived, ITiledMapLayer | undefined>( 10 | currentPage, 11 | ($currentPage, set) => { 12 | loadingConfigurationLayerStore.set(true); 13 | WA.room 14 | .getTiledMap() 15 | .then((tiledMap) => { 16 | const configurationLayer = tiledMap.layers.find( 17 | (layer) => layer.name === "configuration", 18 | ); 19 | 20 | if (configurationLayer === undefined) { 21 | throw new Error( 22 | 'Could not find a layer with the name "configuration" on the map', 23 | ); 24 | } 25 | 26 | set(findLayer($currentPage, configurationLayer as ITiledMapLayer)); 27 | loadingConfigurationLayerStore.set(false); 28 | }) 29 | .catch((e) => { 30 | console.error("Error while loading the configuration layer", e); 31 | loadingConfigurationLayerStore.set(false); 32 | }); 33 | }, 34 | ); 35 | 36 | function findLayer(name: string, configurationLayer: ITiledMapLayer): ITiledMapLayer { 37 | const layer = recursiveFindLayer(name, configurationLayer); 38 | if (layer === undefined) { 39 | throw new Error("Cannot find layer with name " + name); 40 | } 41 | return layer; 42 | } 43 | function recursiveFindLayer(name: string, layer: ITiledMapLayer): ITiledMapLayer | undefined { 44 | if (name === layer.name) { 45 | return layer; 46 | } 47 | 48 | if (layer.type === "group") { 49 | for (const innerLayer of layer.layers) { 50 | const result = recursiveFindLayer(name, innerLayer); 51 | if (result) { 52 | return result; 53 | } 54 | } 55 | } 56 | 57 | return undefined; 58 | } 59 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Stores/NavigationStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const currentPage = writable("configuration"); 4 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/Stores/VariablesStore.ts: -------------------------------------------------------------------------------- 1 | import { derived, Readable } from "svelte/store"; 2 | import { getVariables, VariableDescriptor } from "../../../VariablesExtra"; 3 | import { configurationLayerStore } from "./LayersStore"; 4 | import type { ITiledMapLayer } from "@workadventure/tiled-map-type-guard/dist"; 5 | 6 | export const variablesStore = derived< 7 | Readable, 8 | Map | undefined 9 | >(configurationLayerStore, ($configurationLayerStore, set) => { 10 | // filtered variables are passed via hash in configuration.html URL 11 | let hash = window.location.hash; 12 | 13 | if (hash) { 14 | // Mostly for the local configuration panel 15 | // We remove the "#" in order to split the hash into an array 16 | hash = hash.substring(1); 17 | const variablesToKeep = hash.split(","); 18 | 19 | getVariables(undefined, variablesToKeep) 20 | .then((variables) => { 21 | set(variables); 22 | }) 23 | .catch((e) => console.error(e)); 24 | } else if ($configurationLayerStore) { 25 | // Mostly for the global configuration panel with sections 26 | getVariables($configurationLayerStore.name) 27 | .then((variables) => { 28 | set(variables); 29 | }) 30 | .catch((e) => console.error(e)); 31 | } else { 32 | // For a case where you need to get all variables without any restriction 33 | getVariables() 34 | .then((variables) => { 35 | set(variables); 36 | }) 37 | .catch((e) => console.error(e)); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/configuration.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Configuration 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/index.ts: -------------------------------------------------------------------------------- 1 | import "./style/style.scss"; 2 | import App from "./Components/App.svelte"; 3 | 4 | new App({ 5 | target: document.body, 6 | }); 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /src/Iframes/Configuration/style/style.scss: -------------------------------------------------------------------------------- 1 | @import '@workadventure-style/sweetsky/style/index.scss'; 2 | -------------------------------------------------------------------------------- /src/Iframes/Keypad/index.ts: -------------------------------------------------------------------------------- 1 | import { getLayersMap } from "../../LayersFlattener"; 2 | import { Properties } from "../../Properties"; 3 | import { DtmfPlayer } from "play-dtmf"; 4 | 5 | let code!: string; 6 | let inputCode = ""; 7 | let doorVariable!: string; 8 | const dtmfPlayer = new DtmfPlayer(); 9 | 10 | WA.onInit() 11 | .then(async () => { 12 | const layerName = window.location.hash.substr(1); 13 | 14 | if (!layerName) { 15 | throw new Error('Missing "layer" in hash'); 16 | } 17 | 18 | const layers = await getLayersMap(); 19 | const layer = layers.get(layerName); 20 | 21 | if (layer === undefined) { 22 | throw new Error('Cannot find layer whose name is "' + layerName + '".'); 23 | } 24 | 25 | const properties = new Properties(layer.properties); 26 | const tmpCode = properties.getString("code"); 27 | const codeVariable = properties.getString("codeVariable"); 28 | 29 | if (tmpCode === undefined && codeVariable === undefined) { 30 | throw new Error('Missing "code" or "codeVariable" for layer "' + layerName + '".'); 31 | } 32 | 33 | if (codeVariable) { 34 | const value = WA.state[codeVariable]; 35 | if (value && (typeof value === "string" || typeof value === "number")) { 36 | code = value.toString(); 37 | } 38 | } else { 39 | code = tmpCode as string; 40 | } 41 | 42 | const doorVariableVal = properties.getString("doorVariable"); 43 | 44 | if (doorVariableVal === undefined) { 45 | throw new Error('Missing "doorVariable" for layer "' + layerName + '".'); 46 | } 47 | 48 | doorVariable = doorVariableVal; 49 | 50 | initKeyBindings(); 51 | }) 52 | .catch((e) => console.error(e)); 53 | 54 | function initKeyBindings(): void { 55 | document.querySelectorAll("button").forEach((button) => { 56 | button.addEventListener("mousedown", function () { 57 | dtmfPlayer.play(this.innerText); 58 | }); 59 | 60 | button.addEventListener("mouseup", function () { 61 | dtmfPlayer.stop(); 62 | }); 63 | 64 | button.addEventListener("click", function () { 65 | inputCode += this.innerText.trim(); 66 | if (inputCode.length > code.length) { 67 | inputCode = inputCode.substr(inputCode.length - code.length, code.length); 68 | } 69 | if (inputCode === code) { 70 | WA.state[doorVariable] = true; 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | export {}; 77 | -------------------------------------------------------------------------------- /src/Iframes/Keypad/keypad.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Keypad 5 | 32 | 33 | 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Launcher/index.ts: -------------------------------------------------------------------------------- 1 | import { launchTutorialv1 } from "../../../Features/tutorialv1"; 2 | 3 | console.info("Onboarding script initialized!"); 4 | document.addEventListener("DOMContentLoaded", () => { 5 | launchTutorialv1(); 6 | }); 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Launcher/script.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tutorial 5 | 6 | 7 | 8 | Tutu v1 script launcher 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Components/App.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | 23 | {#if $currentStepStore === 0} 24 | 25 | {/if} 26 | {#if $currentStepStore === 1} 27 | 28 | {/if} 29 | {#if $currentStepStore === 2} 30 | 31 | {/if} 32 | {#if $currentStepStore === 3 } 33 | 34 | {/if} 35 | 36 |
37 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Components/Steps.svelte: -------------------------------------------------------------------------------- 1 | 27 |
28 | 29 | 30 | 31 |
32 | 45 |
46 | 47 | 48 | 49 | 50 | 79 | 80 |
81 | 82 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Components/Steps/Step1.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 | 8 | 9 | 10 |

{i18next.t('tuto.step1.title')}

11 |
12 | 13 |
14 | Walk 15 | Walk 16 |

{i18next.t('tuto.step1.use')} {i18next.t('tuto.step1.arowkeys')} {i18next.t('tuto.step1.or')} {i18next.t('tuto.step1.wasd')} {i18next.t('tuto.step1.aroundTheMap')}

17 | 18 | 19 |

{i18next.t('tuto.step1.tip')} {i18next.t('tuto.step1.describeTip')}

20 |
21 | 22 |
23 | Walk 24 |

{i18next.t('tuto.step1.silentZone')}

25 | 26 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Components/Steps/Step2.svelte: -------------------------------------------------------------------------------- 1 | 4 |
5 | 6 |

{i18next.t("tuto.step2.title")}

7 |
8 | 9 | 10 | Walk 11 |

{i18next.t("tuto.step2.describe")} {i18next.t("tuto.step2.describe2")}

12 |
13 | 14 | 15 |
16 |
Walk 17 |

{i18next.t("tuto.step2.describe3")}

18 |
19 | 20 |
21 | Walk 22 |

{i18next.t("tuto.step2.describe4")}

23 |
24 | 25 |
26 | 27 | 28 | 40 | 46 | 47 | 48 |
49 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Components/Steps/Step3.svelte: -------------------------------------------------------------------------------- 1 | 6 |
7 | 8 |

{i18next.t("tuto.step3.title")}

9 |
10 | 11 | 12 | Walk 13 |

{i18next.t("tuto.step3.interact")} {i18next.t("tuto.step3.likeSign")} {i18next.t("tuto.step3.sometimes")} {i18next.t("tuto.step3.closer")}

14 |
15 | 16 | 17 |
18 |
19 | Walk 20 |

{i18next.t("tuto.step3.describe3")}

21 |
22 | 23 |
24 | Walk 25 |

{i18next.t("tuto.step3.describe4")}

26 |
27 | 28 |
29 | 30 | 31 | 43 | 44 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Components/Steps/Welcome.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
9 | 10 |

{i18next.t('tuto.welcome.title')}

11 |

{i18next.t('tuto.welcome.subtitle')}

12 |
13 | 14 | 15 | 16 |
17 | 18 |

👋

19 |

{i18next.t('tuto.welcome.describe')} {i18next.t('tuto.welcome.describe2')}

20 |

{i18next.t('tuto.welcome.describe3')}

21 | 22 |
23 | 26 |
27 | Walk 28 | {i18next.t('tuto.welcome.bloc1')} 29 |
30 | 31 |
32 | Walk 33 | {i18next.t('tuto.welcome.bloc2')} 34 |
35 |
36 | Walk 37 | {i18next.t('tuto.welcome.bloc3')} 38 |
39 | 40 |
41 |
42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/Store/StepStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | /*const step1Onboarding = "resources/images/tutov1/step1-onboarding.png"; 4 | const step2Onboarding = "resources/images/tutov1/step2-onboarding.png"; 5 | const step3Onboarding = "resources/images/tutov1/step3-onboarding.png"; 6 | const step4Onboarding = "resources/images/tutov1/step4-onboarding.png"; 7 | const step5Onboarding = "resources/images/tutov1/step5-onboarding.png"; 8 | const step6Onboarding = "resources/images/tutov1/step6-onboarding.png";*/ 9 | 10 | /*const step1Onboarding = 11 | "https://workadventure-chat-uploads.s3.eu-west-1.amazonaws.com/upload/video/step1-onboarding.png"; 12 | const step2Onboarding = 13 | "https://workadventure-chat-uploads.s3.eu-west-1.amazonaws.com/upload/video/step2-onboarding.png"; 14 | const step3Onboarding = 15 | "https://workadventure-chat-uploads.s3.eu-west-1.amazonaws.com/upload/video/step3-onboarding.png"; 16 | const step4Onboarding = 17 | "https://workadventure-chat-uploads.s3.eu-west-1.amazonaws.com/upload/video/step4-onboarding.png"; 18 | const step5Onboarding = 19 | "https://workadventure-chat-uploads.s3.eu-west-1.amazonaws.com/upload/video/step5-onboarding.png"; 20 | const step6Onboarding = 21 | "https://workadventure-chat-uploads.s3.eu-west-1.amazonaws.com/upload/video/step6-onboarding.png";*/ 22 | 23 | export const currentStepStore = writable(0); 24 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/index.ts: -------------------------------------------------------------------------------- 1 | import "./style/index.scss"; 2 | 3 | import App from "./Components/App.svelte"; 4 | import i18next from "i18next"; 5 | import * as en from "../../../translate/en-US/index"; 6 | import * as fr from "../../../translate/fr-FR/index"; 7 | 8 | i18next.init({ 9 | debug: true, 10 | fallbackLng: "en", 11 | resources: { 12 | en: { 13 | translation: en, 14 | }, 15 | fr: { 16 | translation: fr, 17 | }, 18 | }, 19 | }); 20 | 21 | const startTuto = () => { 22 | new App({ 23 | target: document.body, 24 | }); 25 | }; 26 | 27 | try { 28 | WA.onInit().then(() => { 29 | const lang = WA.player.language; 30 | i18next.changeLanguage(lang); 31 | startTuto(); 32 | }); 33 | } catch (e) { 34 | console.warn("error", e); 35 | startTuto(); 36 | } 37 | -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/style/index.scss: -------------------------------------------------------------------------------- 1 | @import '@workadventure-style/sweetsky/style/index.scss'; 2 | 3 | video{ 4 | max-width: 760px; 5 | } -------------------------------------------------------------------------------- /src/Iframes/TutorialV1/Tuto/tutorialv1.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tutorial 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/LayersExtra.ts: -------------------------------------------------------------------------------- 1 | import type { ITiledMapTileLayer } from "@workadventure/tiled-map-type-guard/dist/ITiledMapTileLayer"; 2 | 3 | /** 4 | * Returns the boundaries of a given layer as an object with properties: { top: number, left: number, right: number, bottom: number } 5 | * Numbers are expressed in "tiles", not pixels. 6 | */ 7 | export function findLayerBoundaries(layer: ITiledMapTileLayer): { 8 | top: number; 9 | left: number; 10 | right: number; 11 | bottom: number; 12 | } { 13 | let left = Infinity; 14 | let top = Infinity; 15 | let bottom = 0; 16 | let right = 0; 17 | 18 | const data = layer.data; 19 | if (typeof data === "string") { 20 | throw new Error("Unsupported tile layer data stored as string instead of CSV"); 21 | } 22 | 23 | for (let j = 0; j < layer.height; j++) { 24 | for (let i = 0; i < layer.width; i++) { 25 | if (data[i + j * layer.width] !== 0) { 26 | left = Math.min(left, i); 27 | right = Math.max(right, i); 28 | top = Math.min(top, j); 29 | bottom = Math.max(bottom, j); 30 | } 31 | } 32 | } 33 | 34 | return { 35 | top, 36 | left, 37 | right: right + 1, 38 | bottom: bottom + 1, 39 | }; 40 | } 41 | 42 | /** 43 | * Returns the boundaries of a given set of layers as an object with properties: { top: number, left: number, right: number, bottom: number } 44 | * Numbers are expressed in "tiles", not pixels. 45 | */ 46 | export function findLayersBoundaries(layers: ITiledMapTileLayer[]): { 47 | top: number; 48 | left: number; 49 | right: number; 50 | bottom: number; 51 | } { 52 | let left = Infinity; 53 | let top = Infinity; 54 | let bottom = 0; 55 | let right = 0; 56 | 57 | for (const layer of layers) { 58 | const boundaries = findLayerBoundaries(layer); 59 | if (boundaries.left < left) { 60 | left = boundaries.left; 61 | } 62 | if (boundaries.top < top) { 63 | top = boundaries.top; 64 | } 65 | if (boundaries.right > right) { 66 | right = boundaries.right; 67 | } 68 | if (boundaries.bottom > bottom) { 69 | bottom = boundaries.bottom; 70 | } 71 | } 72 | 73 | return { 74 | top, 75 | left, 76 | right, 77 | bottom, 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /src/LayersFlattener.ts: -------------------------------------------------------------------------------- 1 | import type { ITiledMap, ITiledMapLayer } from "@workadventure/tiled-map-type-guard"; 2 | 3 | let layersMapPromise: Promise> | undefined = undefined; 4 | 5 | /** 6 | * Returns a map of all layers in a uni-dimensional map. 7 | * Layers are renamed: if they are in a group layer, the name of the group layer is prepended with a "/" as a separator. 8 | * Layers are indexed by name. 9 | */ 10 | export async function getLayersMap(): Promise> { 11 | if (layersMapPromise === undefined) { 12 | layersMapPromise = getLayersMapWithoutCache(); 13 | } 14 | return layersMapPromise; 15 | } 16 | 17 | async function getLayersMapWithoutCache(): Promise> { 18 | return flattenGroupLayersMap((await WA.room.getTiledMap()) as ITiledMap); 19 | } 20 | 21 | /** 22 | * Flatten the grouped layers 23 | */ 24 | function flattenGroupLayersMap(map: ITiledMap): Map { 25 | const flatLayers = new Map(); 26 | flattenGroupLayers(map.layers, "", flatLayers); 27 | return flatLayers; 28 | } 29 | 30 | function flattenGroupLayers( 31 | layers: ITiledMapLayer[], 32 | prefix: string, 33 | flatLayers: Map, 34 | ): void { 35 | for (const layer of layers) { 36 | if (layer.type === "group") { 37 | flattenGroupLayers(layer.layers, prefix + layer.name + "/", flatLayers); 38 | } else { 39 | layer.name = prefix + layer.name; 40 | flatLayers.set(layer.name, layer); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Properties.ts: -------------------------------------------------------------------------------- 1 | import type { ITiledMapProperty, Json } from "@workadventure/tiled-map-type-guard"; 2 | 3 | export class Properties { 4 | private properties: ITiledMapProperty[]; 5 | 6 | public constructor(properties: ITiledMapProperty[] | undefined) { 7 | this.properties = properties ?? []; 8 | } 9 | 10 | public get(name: string): ITiledMapProperty["value"] { 11 | const values = this.properties 12 | .filter((property) => property.name === name) 13 | .map((property) => property.value); 14 | if (values.length > 1) { 15 | throw new Error('Expected only one property to be named "' + name + '"'); 16 | } 17 | if (values.length === 0) { 18 | return undefined; 19 | } 20 | return values[0]; 21 | } 22 | 23 | public getString(name: string): string | undefined { 24 | return this.getByType(name, "string") as string | undefined; 25 | } 26 | 27 | public getNumber(name: string): number | undefined { 28 | return this.getByType(name, "number") as number | undefined; 29 | } 30 | 31 | public getBoolean(name: string): boolean | undefined { 32 | return this.getByType(name, "boolean") as boolean | undefined; 33 | } 34 | 35 | private getByType( 36 | name: string, 37 | type: "string" | "number" | "boolean" | "json", 38 | ): string | boolean | number | Json | undefined { 39 | const value = this.get(name); 40 | if (value === undefined) { 41 | return undefined; 42 | } 43 | if (type !== "json" && typeof value !== type) { 44 | throw new Error('Expected property "' + name + '" to have type "' + type + '"'); 45 | } 46 | return value; 47 | } 48 | 49 | public mustGetString(name: string): string { 50 | return this.mustGetByType(name, "string") as string; 51 | } 52 | 53 | public mustGetNumber(name: string): number { 54 | return this.mustGetByType(name, "number") as number; 55 | } 56 | 57 | public mustGetBoolean(name: string): boolean { 58 | return this.mustGetByType(name, "boolean") as boolean; 59 | } 60 | 61 | private mustGetByType( 62 | name: string, 63 | type: "string" | "number" | "boolean" | "json", 64 | ): string | boolean | number | Json | undefined { 65 | const value = this.get(name); 66 | if (value === undefined) { 67 | throw new Error('Property "' + name + '" is missing'); 68 | } 69 | if (type !== "json" && typeof value !== type) { 70 | throw new Error('Expected property "' + name + '" to have type "' + type + '"'); 71 | } 72 | return value; 73 | } 74 | 75 | /** 76 | * Returns the type of property "name" or undefined if the property is not defined. 77 | */ 78 | public getType(name: string): string | undefined { 79 | const types = this.properties 80 | .filter((property) => property.name === name) 81 | .map((property) => property.type); 82 | if (types.length > 1) { 83 | throw new Error('Expected only one property to be named "' + name + '"'); 84 | } 85 | if (types.length === 0) { 86 | return undefined; 87 | } 88 | return types[0]; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/TemplateValue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A value of a property that can be evaluated / tracked using Mustache templates 3 | */ 4 | import type { WorkadventureStateCommands } from "@workadventure/iframe-api-typings/play/src/front/Api/Iframe/state"; 5 | import Mustache from "mustache"; 6 | 7 | type RAW_VALUE = "text"; 8 | type ESCAPED_VALUE = "name"; 9 | type UNESCAPED_VALUE = "&"; 10 | type SECTION = "#"; 11 | type INVERTED = "^"; 12 | type COMMENT = "!"; 13 | type PARTIAL = ">"; 14 | type EQUAL = "="; 15 | 16 | type TemplateSpanType = 17 | | RAW_VALUE 18 | | ESCAPED_VALUE 19 | | SECTION 20 | | UNESCAPED_VALUE 21 | | INVERTED 22 | | COMMENT 23 | | PARTIAL 24 | | EQUAL; 25 | 26 | type TemplateSpans = Array< 27 | | [TemplateSpanType, string, number, number] 28 | | [TemplateSpanType, string, number, number, TemplateSpans, number] 29 | | [TemplateSpanType, string, number, number, string, number, boolean] 30 | >; 31 | 32 | export class TemplateValue { 33 | private readonly ast: TemplateSpans; 34 | private value: string | undefined; 35 | 36 | constructor(private template: string, private state: WorkadventureStateCommands) { 37 | this.ast = Mustache.parse(template); 38 | } 39 | 40 | public getValue(): string { 41 | if (this.value === undefined) { 42 | this.value = Mustache.render(this.template, this.state); 43 | } 44 | return this.value; 45 | } 46 | 47 | public onChange(callback: (newValue: string) => void): { unsubscribe: () => void } { 48 | const subscriptions: { unsubscribe(): void }[] = []; 49 | for (const variableName of this.getUsedVariables().values()) { 50 | subscriptions.push( 51 | this.state.onVariableChange(variableName).subscribe(() => { 52 | const newValue = Mustache.render(this.template, this.state); 53 | if (newValue !== this.value) { 54 | this.value = newValue; 55 | callback(this.value); 56 | } 57 | }), 58 | ); 59 | } 60 | return { 61 | unsubscribe: () => { 62 | for (const subscription of subscriptions) { 63 | subscription.unsubscribe(); 64 | } 65 | }, 66 | }; 67 | } 68 | 69 | public isPureString(): boolean { 70 | return this.ast.length === 0 || (this.ast.length === 1 && this.ast[0][0] === "text"); 71 | } 72 | 73 | public getUsedVariables(): Set { 74 | const variables = new Set(); 75 | this.recursiveGetUsedVariables(this.ast, variables); 76 | return variables; 77 | } 78 | 79 | private recursiveGetUsedVariables(ast: TemplateSpans, variables: Set): void { 80 | for (const token of ast) { 81 | const type = token[0]; 82 | const name = token[1]; 83 | const subAst = token[4]; 84 | if (["name", "&", "#", "^"].includes(type)) { 85 | variables.add(name); 86 | } 87 | if (subAst !== undefined && typeof subAst !== "string") { 88 | this.recursiveGetUsedVariables(subAst, variables); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/VariableMapper.ts: -------------------------------------------------------------------------------- 1 | import type { Readable, Writable } from "svelte/store"; 2 | import { get, writable } from "svelte/store"; 3 | 4 | /** 5 | * A function that maps a WorkAdventure variable to a Svelte store 6 | * 7 | * The store is initialized with the value of the variable. 8 | */ 9 | export function mapVariableToStore( 10 | variableName: string, 11 | store: Readable & { set(this: void, value: unknown): void }, 12 | isLoadingVariableStore: Readable & { set(this: void, value: boolean): void }, 13 | ): void { 14 | store.set(WA.state.loadVariable(variableName)); 15 | 16 | store.subscribe(async (value) => { 17 | if (value !== WA.state.loadVariable(variableName)) { 18 | try { 19 | await WA.state.saveVariable(variableName, value); 20 | isLoadingVariableStore.set(false); 21 | console.info(`Variable ${variableName} saved`); 22 | } catch (e) { 23 | console.info(`Error while saving variable ${variableName}`, e); 24 | isLoadingVariableStore.set(false); 25 | throw e; 26 | } 27 | } 28 | }); 29 | 30 | WA.state.onVariableChange(variableName).subscribe((value: unknown) => { 31 | if (value !== get(store)) { 32 | store.set(value); 33 | } 34 | }); 35 | } 36 | 37 | /** 38 | * Returns a new writable Svelte store generated from a WorkAdventure variable. 39 | * 40 | * The store is initialized with the value of the variable. 41 | */ 42 | export function createStoreFromVariable(variableName: string): { 43 | isLoadingVariableStore: Writable; 44 | store: Writable; 45 | } { 46 | const store = writable(undefined); 47 | const isLoadingVariableStore = writable(false); 48 | mapVariableToStore(variableName, store, isLoadingVariableStore); 49 | return { store, isLoadingVariableStore }; 50 | } 51 | -------------------------------------------------------------------------------- /src/VariablesExtra.ts: -------------------------------------------------------------------------------- 1 | import type { ITiledMapLayer, ITiledMapObject } from "@workadventure/tiled-map-type-guard/dist"; 2 | import { Properties } from "./Properties"; 3 | import { defaultAssetsUrl } from "./Features/default_assets_url"; 4 | 5 | export class VariableDescriptor { 6 | public readonly name: string; 7 | public readonly x: number; 8 | public readonly y: number; 9 | public readonly properties: Properties; 10 | 11 | public constructor(object: ITiledMapObject) { 12 | this.name = object.name; 13 | this.x = object.x; 14 | this.y = object.y; 15 | this.properties = new Properties(object.properties); 16 | } 17 | 18 | public get isReadable(): boolean { 19 | const readableBy = this.properties.getString("readableBy"); 20 | if (!readableBy) { 21 | return true; 22 | } 23 | return WA.player.tags.includes(readableBy); 24 | } 25 | 26 | public get isWritable(): boolean { 27 | const writableBy = this.properties.getString("writableBy"); 28 | if (!writableBy) { 29 | return true; 30 | } 31 | return WA.player.tags.includes(writableBy); 32 | } 33 | } 34 | 35 | /** 36 | * Opens the local configuration panel. 37 | * You can filter which variables to configure by passing their names in parameter. 38 | */ 39 | export function openConfig(variables?: string[]): void { 40 | const parameters = variables ? "#" + variables.join() : ""; 41 | WA.nav.openCoWebSite(defaultAssetsUrl + "/configuration.html" + parameters, true); 42 | } 43 | 44 | export async function getVariables( 45 | layerFilter?: string, 46 | variablesFilter?: Array, 47 | ): Promise> { 48 | const map = await WA.room.getTiledMap(); 49 | const variables = new Map(); 50 | 51 | getAllVariablesRecursive( 52 | map.layers as ITiledMapLayer[], 53 | variables, 54 | layerFilter, 55 | variablesFilter, 56 | ); 57 | 58 | return variables; 59 | } 60 | 61 | function getAllVariablesRecursive( 62 | layers: ITiledMapLayer[], 63 | variables: Map, 64 | layerFilter?: string, 65 | variablesFilter?: Array, 66 | ): void { 67 | for (const layer of layers) { 68 | if (layer.type === "objectgroup") { 69 | for (const object of layer.objects) { 70 | if (object.type === "variable" || object.class === "variable") { 71 | // Here we now that we are looking at a variable 72 | // but depending on the cases, only some variables should be added to the map (shown in the configuration panel) 73 | 74 | // In this case: we only want to keep the variables of a specific layer 75 | if (!!layerFilter && layer.name !== layerFilter) continue; 76 | // In this case: we only want to keep the variables with a specific name 77 | if (!!variablesFilter && !variablesFilter.includes(object.name)) continue; 78 | 79 | variables.set(object.name, new VariableDescriptor(object)); 80 | } 81 | } 82 | } else if (layer.type === "group") { 83 | // If the current layer is a group, re-run the same method with its layers 84 | getAllVariablesRecursive(layer.layers, variables, layerFilter, variablesFilter); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // This file automatically bootstraps all the features on WA initialization. 2 | // Importing this file creates a number of side effects. 3 | 4 | import { bootstrapExtra } from "./init"; 5 | 6 | bootstrapExtra(); 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference 2 | /// 3 | 4 | export * from "./VariablesExtra"; 5 | export * from "./Properties"; 6 | export * from "./AreaObject"; 7 | export * from "./LayersFlattener"; 8 | export * from "./LayersExtra"; 9 | export * from "./Features/properties_templates"; 10 | export * from "./Features/doors"; 11 | export * from "./Features/variable_actions"; 12 | export * from "./Features/tutorialv1"; 13 | export * from "./init"; 14 | -------------------------------------------------------------------------------- /src/init.ts: -------------------------------------------------------------------------------- 1 | import { initDoors } from "./Features/doors"; 2 | import { initSpecialProperties } from "./Features/special_properties"; 3 | import { initConfiguration } from "./Features/configuration"; 4 | import { 5 | initPropertiesTemplates, 6 | initPropertiesTemplatesArea, 7 | } from "./Features/properties_templates"; 8 | 9 | /** 10 | * Bootstraps all the features of the extra library. 11 | * This function must be called once if you are importing this library in your own WorkAdventure script. 12 | */ 13 | export function bootstrapExtra(): Promise { 14 | return WA.onInit() 15 | .then(() => { 16 | initDoors().catch((e) => console.error(e)); 17 | initSpecialProperties().catch((e) => console.error(e)); 18 | initConfiguration().catch((e) => console.error(e)); 19 | initPropertiesTemplates().catch((e) => console.error(e)); 20 | initPropertiesTemplatesArea().catch((e) => console.error(e)); 21 | }) 22 | .catch((e) => console.error(e)); 23 | } 24 | -------------------------------------------------------------------------------- /src/translate/en-US/index.ts: -------------------------------------------------------------------------------- 1 | export { default as tuto } from "./tuto"; 2 | -------------------------------------------------------------------------------- /src/translate/en-US/tuto.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | videoNotLoaded: "Video not available in your navigator", 3 | next: "Next", 4 | previous: "Previous", 5 | skipAll: "Skip all", 6 | finish: "Finish", 7 | skipTutorial: "Skip tutorial", 8 | startTutorial: "Start tutorial", 9 | welcome: { 10 | title: "Welcome aboard!", 11 | subtitle: 12 | "This quick guide takes just 2 minutes, it will help you get comfortable with the platform in just a few steps.", 13 | describe: "It’s a virtual world where teams meet, talk, and collaborate naturally", 14 | describe2: " — just like in real life.", 15 | describe3: "In this tutorial, you’ll learn how to:", 16 | bloc1: "Move around", 17 | bloc2: "Talk with others", 18 | bloc3: "Discover interactive objects", 19 | }, 20 | step1: { 21 | title: "Explore your world", 22 | use: "Use your", 23 | arrowkeys: " arrow keys", 24 | or: "or", 25 | wasd: "WASD to move your avatar", 26 | aroundTheMap: "around the map. Try it now!", 27 | tip: "Tip:", 28 | describeTip: 29 | "Hold the Shift key while moving to walk faster — perfect if you’re running late or want to explore more quickly!", 30 | silentZone: "You’re currently in a Silent Zone — here, no one can hear you.", 31 | }, 32 | step2: { 33 | title: "Talk & collaborate", 34 | describe: "To start a conversation, approach someone ", 35 | describe2: " — video or audio will launch automatically.", 36 | describe3: "Click the padlock icon to lock the conversation and make it private.", 37 | describe4: "Click the “Share Screen” button during a conversation to show your screen", 38 | }, 39 | step3: { 40 | title: "Discover & Interact", 41 | interact: "Interact with objects ", 42 | likeSign: "like signs, screens, documents and portals", 43 | sometimes: "— sometimes they light up ", 44 | closer: "when you get closer.", 45 | describe3: 46 | "Gather people in meeting rooms, enhancing spontaneous discussions and teamwork.", 47 | describe4: 48 | "Take a break in silent zones, the perfect place to focus and to work without interruptions.", 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /src/translate/fr-FR/index.ts: -------------------------------------------------------------------------------- 1 | export { default as tuto } from "./tuto"; 2 | -------------------------------------------------------------------------------- /src/translate/fr-FR/tuto.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | videoNotLoaded: "Vidéo non disponible dans votre navigateur", 3 | next: "Suivant", 4 | previous: "Précédent", 5 | skipAll: "Passer", 6 | finish: "Terminer", 7 | skipTutorial: "Passer le tutoriel", 8 | startTutorial: "Démarrer le tutoriel", 9 | welcome: { 10 | title: "Bienvenue à bord!", 11 | subtitle: 12 | "Ce guide rapide ne prend que 2 minutes, il vous aidera à vous familiariser avec la plateforme en quelques étapes seulement.", 13 | describe: 14 | "C'est un monde virtuel où les équipes se rencontrent, discutent et collaborent naturellement", 15 | describe2: " — comme dans la vraie vie.", 16 | describe3: "Dans ce tutoriel, vous apprendrez à :", 17 | bloc1: "Se déplacer", 18 | bloc2: "Parlez avec les autres", 19 | bloc3: "Découvrez des objets interactifs", 20 | }, 21 | step1: { 22 | title: "Explorez votre monde", 23 | use: "Utilisez les", 24 | arrowkeys: "flèches directionnelles", 25 | or: "ou ", 26 | wasd: "ZQSD pour déplacer votre avatar", 27 | aroundTheMap: " sur la carte. Essayez-le maintenant !", 28 | tip: "Astuce:", 29 | describeTip: 30 | " maintenez la touche Maj enfoncée pendant que vous vous déplacez pour marcher plus vite — parfait si vous êtes en retard ou si vous souhaitez explorer plus rapidement !", 31 | silentZone: 32 | "Vous êtes actuellement dans une zone silencieuse — ici, personne ne peut vous entendre.", 33 | }, 34 | step2: { 35 | title: "Parlez & collaborez", 36 | describe: "Pour démarrer une conversation, approchez-vous de quelqu'un", 37 | describe2: " — la vidéo ou l'audio se lancera automatiquement.", 38 | describe3: 39 | "Cliquez sur l'icône du cadenas pour verrouiller la conversation et la rendre privée.", 40 | describe4: 41 | "Cliquez sur le bouton « Partager l'écran » pendant une conversation pour afficher votre écran", 42 | }, 43 | step3: { 44 | title: "Découvrez & interagissez", 45 | interact: "Interagissez avec des objets ", 46 | likeSign: " tels que des panneaux, des écrans, des documents et des portails", 47 | sometimes: "— parfois ils s'allument quand on s'approche.", 48 | closer: "quand on s'approche.", 49 | describe3: 50 | "Réunissez les gens dans des salles de réunion, favorisant les discussions spontanées et le travail d'équipe.", 51 | describe4: 52 | "Faites une pause dans des zones silencieuses, l'endroit idéal pour vous concentrer et travailler sans interruption.", 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "play-dtmf" { 2 | export const DtmfPlayer: any; // eslint-disable-line 3 | } 4 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindConfig = require("@workadventure-style/sweetsky/tailwind.config.cjs"); 2 | module.exports = tailwindConfig; 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const tailwindConfig = require("@workadventure-style/sweetsky/tailwind.config"); 2 | module.exports = tailwindConfig; 3 | -------------------------------------------------------------------------------- /test/maps/Door2_pipo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/test/maps/Door2_pipo.png -------------------------------------------------------------------------------- /test/maps/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 20 | 21 | 22 | 25 | 28 | 29 | 30 | 33 | 36 | 37 | 38 | 41 | 44 | 45 | 46 | 49 | 52 | 53 | 54 | 57 | 60 | 61 | 62 |
ResultTest
15 | Success Failure Pending 16 | 18 | Testing Doors 19 |
23 | Success Failure Pending 24 | 26 | Testing Doors with automatic open/close doorstep 27 |
31 | Success Failure Pending 32 | 34 | Testing Doors with automatic open/close doorstep defined as objects 35 |
39 | Success Failure Pending 40 | 42 | Testing Doors with access code 43 |
47 | Success Failure Pending 48 | 50 | Testing automatic configuration 51 |
55 | Success Failure Pending 56 | 58 | Testing automatic configuration with multiple sections 59 |
63 | 64 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /test/maps/special-zones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/test/maps/special-zones.png -------------------------------------------------------------------------------- /test/maps/tileset1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/test/maps/tileset1.png -------------------------------------------------------------------------------- /test/maps/tileset2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/test/maps/tileset2.png -------------------------------------------------------------------------------- /test/maps/walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workadventure/scripting-api-extra/f0efe18cb13ec30dfecb02ee44bef615290e579e/test/maps/walls.png -------------------------------------------------------------------------------- /test/setup-test.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(30000); 2 | -------------------------------------------------------------------------------- /test/unit/TemplateValue.spec.ts: -------------------------------------------------------------------------------- 1 | import type { WorkadventureStateCommands } from "@workadventure/iframe-api-typings/play/src/front/Api/Iframe/state"; 2 | import { TemplateValue } from "../../src/TemplateValue"; 3 | 4 | describe("Test TemplateValue", () => { 5 | function buildTemplateValue(template: string): TemplateValue { 6 | return new TemplateValue(template, {} as WorkadventureStateCommands); 7 | } 8 | 9 | it("isPureString works", () => { 10 | expect(buildTemplateValue("foo").isPureString()).toBe(true); 11 | expect(buildTemplateValue("foo bar \n baz").isPureString()).toBe(true); 12 | expect(buildTemplateValue("foo bar \n {{ baz }}").isPureString()).toBe(false); 13 | expect(buildTemplateValue("").isPureString()).toBe(true); 14 | expect(buildTemplateValue("{{#repos}}{{name}}{{/repos}}").isPureString()).toBe( 15 | false, 16 | ); 17 | }); 18 | 19 | it("finds used variables", () => { 20 | expect(buildTemplateValue("foo").getUsedVariables().size).toBe(0); 21 | expect(buildTemplateValue("foo bar \n {{ baz }}").getUsedVariables().has("baz")).toBe(true); 22 | const template = buildTemplateValue("{{#repos}}{{name}}{{/repos}}"); 23 | expect(template.getUsedVariables().has("repos")).toBe(true); 24 | expect(template.getUsedVariables().has("name")).toBe(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tsconfig-for-webpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "esModuleInterop": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2019", 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "rootDirs": ["./", "./src"], 8 | // "rootDir": "src", 9 | "baseUrl": ".", 10 | "typeRoots": ["./src/types", "./@types", "node_modules/@types"], 11 | "types": ["node", "jest", "svelte"] 12 | }, 13 | "exclude": ["dist", "node_modules", "test"] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "allowJs": false, 6 | "removeComments": true, 7 | "isolatedModules": true, 8 | "declaration": true, 9 | // "target": "ES2019", 10 | // "sourceMap": true, 11 | // "outDir": "./dist", 12 | // "rootDirs": ["./", "./src"], 13 | // "typeRoots": ["./types", "./@types", "node_modules/@types"], 14 | // "types": ["node", "jest"], 15 | 16 | "strict": true, 17 | "noImplicitAny": true, 18 | "strictNullChecks": true, 19 | "strictFunctionTypes": true, 20 | "strictBindCallApply": true, 21 | "strictPropertyInitialization": true, 22 | "noImplicitThis": true, 23 | "alwaysStrict": true, 24 | 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | "noImplicitReturns": true, 28 | "noFallthroughCasesInSwitch": true, 29 | 30 | "moduleResolution": "node", 31 | "esModuleInterop": true, 32 | "module": "ESNext", 33 | 34 | "experimentalDecorators": true, 35 | "emitDecoratorMetadata": true, 36 | 37 | "skipLibCheck": true, 38 | "forceConsistentCasingInFileNames": true, 39 | 40 | "preserveConstEnums": true, 41 | "suppressImplicitAnyIndexErrors": true, 42 | "incremental": true, 43 | 44 | "downlevelIteration": true, 45 | 46 | "types": ["node", "jest", "svelte"] 47 | }, 48 | "include": ["src"], 49 | "exclude": [ 50 | "node_modules", 51 | "./node_modules", 52 | "./node_modules/*", 53 | "./node_modules/@types/node/index.d.ts", 54 | "coverage" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | 4 | "include": [] 5 | } -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import type { Configuration } from "webpack"; 2 | import type WebpackDevServer from "webpack-dev-server"; 3 | import path from "path"; 4 | import webpack from "webpack"; 5 | import HtmlWebpackPlugin from "html-webpack-plugin"; 6 | import MiniCssExtractPlugin from "mini-css-extract-plugin"; 7 | import sveltePreprocess from "svelte-preprocess"; 8 | //import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; 9 | import NodePolyfillPlugin from "node-polyfill-webpack-plugin"; 10 | import CopyPlugin from "copy-webpack-plugin"; 11 | 12 | const mode = process.env.NODE_ENV ?? "development"; 13 | const isProduction = mode === "production"; 14 | const isDevelopment = !isProduction; 15 | 16 | const resources = [{ from: "resources", to: "resources" }]; 17 | 18 | if (isDevelopment) { 19 | resources.push({ 20 | from: "test/maps", 21 | to: ".", 22 | }); 23 | } 24 | 25 | module.exports = { 26 | entry: { 27 | main: "./src/bootstrap.ts", 28 | keypad: "./src/Iframes/Keypad/index.ts", 29 | configuration: "./src/Iframes/Configuration/index.ts", 30 | tutorialv1: "./src/Iframes/TutorialV1/Tuto/index.ts", 31 | tutorialv1Script: "./src/Iframes/TutorialV1/Launcher/index.ts", 32 | }, 33 | mode: mode, 34 | devtool: isDevelopment ? "inline-source-map" : "source-map", 35 | //devtool: isDevelopment ? 'eval' : 'source-map', 36 | devServer: { 37 | port: 3000, 38 | static: ["dist"], 39 | host: "0.0.0.0", 40 | allowedHosts: "all", 41 | headers: { 42 | "Access-Control-Allow-Origin": "*", 43 | "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", 44 | "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization", 45 | }, 46 | }, 47 | module: { 48 | rules: [ 49 | { 50 | test: /\.tsx?$/, 51 | //use: 'ts-loader', 52 | exclude: /node_modules/, 53 | loader: "ts-loader", 54 | options: { 55 | //transpileOnly: true, 56 | configFile: "tsconfig.build.json", 57 | }, 58 | }, 59 | { 60 | test: /\.scss$/, 61 | exclude: /node_modules/, 62 | use: [ 63 | MiniCssExtractPlugin.loader, 64 | { 65 | loader: "css-loader", 66 | options: { 67 | url: false, 68 | sourceMap: true, 69 | }, 70 | }, 71 | "postcss-loader", 72 | "sass-loader", 73 | ], 74 | }, 75 | { 76 | test: /\.css$/, 77 | exclude: /node_modules/, 78 | use: [ 79 | MiniCssExtractPlugin.loader, 80 | { 81 | loader: "css-loader", 82 | options: { 83 | //url: false, 84 | sourceMap: true, 85 | }, 86 | }, 87 | ], 88 | }, 89 | { 90 | test: /\.svelte$/, 91 | exclude: /node_modules/, 92 | use: { 93 | loader: "svelte-loader", 94 | options: { 95 | compilerOptions: { 96 | // Dev mode must be enabled for HMR to work! 97 | dev: isDevelopment, 98 | }, 99 | emitCss: isProduction, 100 | hotReload: isDevelopment, 101 | hotOptions: { 102 | // List of options and defaults: https://www.npmjs.com/package/svelte-loader-hot#usage 103 | noPreserveState: false, 104 | optimistic: true, 105 | }, 106 | preprocess: sveltePreprocess({ 107 | scss: true, 108 | sass: true, 109 | }), 110 | onwarn: function ( 111 | warning: { code: string }, 112 | handleWarning: (warning: { code: string }) => void, 113 | ) { 114 | // See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782 115 | 116 | /*if (warning.code === 'a11y-no-onchange') { return } 117 | if (warning.code === 'a11y-autofocus') { return } 118 | if (warning.code === 'a11y-media-has-caption') { return }*/ 119 | 120 | // process as usual 121 | handleWarning(warning); 122 | }, 123 | }, 124 | }, 125 | }, 126 | 127 | // Required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4 128 | // See: https://github.com/sveltejs/svelte-loader#usage 129 | { 130 | test: /node_modules\/svelte\/.*\.mjs$/, 131 | resolve: { 132 | fullySpecified: false, 133 | }, 134 | }, 135 | { 136 | test: /\.(eot|svg|png|gif|jpg)$/, 137 | exclude: /node_modules/, 138 | type: "asset", 139 | }, 140 | { 141 | test: /\.(woff(2)?|ttf)$/, 142 | type: "asset", 143 | generator: { 144 | filename: "fonts/[name][ext]", 145 | }, 146 | }, 147 | ], 148 | }, 149 | resolve: { 150 | alias: { 151 | svelte: path.resolve("node_modules", "svelte"), 152 | }, 153 | extensions: [".tsx", ".ts", ".js", ".scss", ".svelte"], 154 | mainFields: ["svelte", "browser", "module", "main", "iframe"], 155 | }, 156 | output: { 157 | filename: (pathData) => { 158 | // Add a content hash only for the main bundle. 159 | // We want the iframe_api.js file to keep its name as it will be referenced from outside iframes. 160 | //return pathData.chunk?.name === 'main' ? 'js/[name].js': '[name].[contenthash].js'; 161 | return pathData.chunk?.name === "main" ? "bundle.js" : "js/[name].[contenthash].js"; 162 | }, 163 | path: path.resolve(__dirname, "dist"), 164 | }, 165 | plugins: [ 166 | new webpack.HotModuleReplacementPlugin(), 167 | //new ForkTsCheckerWebpackPlugin({ 168 | //eslint: { 169 | // files: './src/**/*.ts' 170 | //} 171 | //}), 172 | new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }), 173 | new HtmlWebpackPlugin({ 174 | template: "./src/Iframes/Keypad/keypad.ejs", 175 | filename: "keypad.html", 176 | templateParameters: { 177 | workadventure_url: process.env.WORKADVENTURE_URL 178 | ? process.env.WORKADVENTURE_URL 179 | : "https://play.workadventu.re", 180 | }, 181 | minify: { 182 | collapseWhitespace: true, 183 | keepClosingSlash: true, 184 | removeComments: false, 185 | removeRedundantAttributes: true, 186 | removeScriptTypeAttributes: true, 187 | removeStyleLinkTypeAttributes: true, 188 | useShortDoctype: true, 189 | }, 190 | chunks: ["keypad"], 191 | }), 192 | new HtmlWebpackPlugin({ 193 | template: "./src/Iframes/Configuration/configuration.ejs", 194 | filename: "configuration.html", 195 | templateParameters: { 196 | workadventure_url: process.env.WORKADVENTURE_URL 197 | ? process.env.WORKADVENTURE_URL 198 | : "https://play.workadventu.re", 199 | }, 200 | minify: { 201 | collapseWhitespace: true, 202 | keepClosingSlash: true, 203 | removeComments: false, 204 | removeRedundantAttributes: true, 205 | removeScriptTypeAttributes: true, 206 | removeStyleLinkTypeAttributes: true, 207 | useShortDoctype: true, 208 | }, 209 | chunks: ["configuration"], 210 | }), 211 | 212 | // Scripting for tutorial V1 213 | new HtmlWebpackPlugin({ 214 | template: "./src/Iframes/TutorialV1/Tuto/tutorialv1.ejs", 215 | filename: "tutorialv1.html", 216 | templateParameters: { 217 | workadventure_url: process.env.WORKADVENTURE_URL 218 | ? process.env.WORKADVENTURE_URL 219 | : "https://play.workadventu.re", 220 | }, 221 | minify: { 222 | collapseWhitespace: true, 223 | keepClosingSlash: true, 224 | removeComments: false, 225 | removeRedundantAttributes: true, 226 | removeScriptTypeAttributes: true, 227 | removeStyleLinkTypeAttributes: true, 228 | useShortDoctype: true, 229 | }, 230 | chunks: ["tutorialv1"], 231 | }), 232 | new HtmlWebpackPlugin({ 233 | template: "./src/Iframes/TutorialV1/Launcher/script.ejs", 234 | filename: "tutorialv1Script.html", 235 | templateParameters: { 236 | workadventure_url: process.env.WORKADVENTURE_URL 237 | ? process.env.WORKADVENTURE_URL 238 | : "https://play.workadventu.re", 239 | }, 240 | minify: { 241 | collapseWhitespace: true, 242 | keepClosingSlash: true, 243 | removeComments: false, 244 | removeRedundantAttributes: true, 245 | removeScriptTypeAttributes: true, 246 | removeStyleLinkTypeAttributes: true, 247 | useShortDoctype: true, 248 | }, 249 | chunks: ["tutorialv1Script"], 250 | }), 251 | 252 | new HtmlWebpackPlugin({ 253 | template: "./test/maps/index.ejs", 254 | filename: "index.html", 255 | templateParameters: { 256 | workadventure_url: process.env.WORKADVENTURE_URL 257 | ? process.env.WORKADVENTURE_URL 258 | : "https://play.workadventu.re", 259 | }, 260 | minify: { 261 | collapseWhitespace: true, 262 | keepClosingSlash: true, 263 | removeComments: false, 264 | removeRedundantAttributes: true, 265 | removeScriptTypeAttributes: true, 266 | removeStyleLinkTypeAttributes: true, 267 | useShortDoctype: true, 268 | }, 269 | chunks: [], 270 | }), 271 | new NodePolyfillPlugin(), 272 | new CopyPlugin({ 273 | patterns: resources, 274 | }), 275 | new webpack.EnvironmentPlugin({ 276 | WORKADVENTURE_URL: process.env.WORKADVENTURE_URL 277 | ? JSON.stringify(process.env.WORKADVENTURE_URL) 278 | : null, 279 | }), 280 | ], 281 | } as Configuration & WebpackDevServer.Configuration; 282 | --------------------------------------------------------------------------------