diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index ca2a01cb..a90d9397 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -61,6 +61,10 @@ jobs: run: yarn run lint working-directory: "front" + - name: "Pretty" + run: yarn run pretty + working-directory: "front" + - name: "Jasmine" run: yarn test working-directory: "front" diff --git a/docs/maps/api-state.md b/docs/maps/api-state.md index 1cc4f7fb..a8ee5589 100644 --- a/docs/maps/api-state.md +++ b/docs/maps/api-state.md @@ -14,7 +14,7 @@ WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subsc WA.state.[any property]: unknown ``` -These methods and properties can be used to save, load and track changes in variables related to the current room. +These methods and properties can be used to save, load and track changes in [variables related to the current room](variables.md). Variables stored in `WA.state` can be any value that is serializable in JSON. @@ -63,44 +63,11 @@ that you get the expected type). For security reasons, the list of variables you are allowed to access and modify is **restricted** (otherwise, anyone on your map could set any data). Variables storage is subject to an authorization process. Read below to learn more. -### Declaring allowed keys +## Defining a variable -In order to declare allowed keys related to a room, you need to add **objects** in an "object layer" of the map. - -Each object will represent a variable. - -
-
- -
-
- -The name of the variable is the name of the object. -The object **type** MUST be **variable**. - -You can set a default value for the object in the `default` property. - -### Persisting variables state - -Use the `persist` property to save the state of the variable in database. If `persist` is false, the variable will stay -in the memory of the WorkAdventure servers but will be wiped out of the memory as soon as the room is empty (or if the -server restarts). - -{.alert.alert-info} -Do not use `persist` for highly dynamic values that have a short life spawn. - -### Managing access rights to variables - -With `readableBy` and `writableBy`, you control who can read of write in this variable. The property accepts a string -representing a "tag". Anyone having this "tag" can read/write in the variable. - -{.alert.alert-warning} -`readableBy` and `writableBy` are specific to the "online" version of WorkAdventure because the notion of tags -is not available unless you have an "admin" server (that is not part of the self-hosted version of WorkAdventure). - -Finally, the `jsonSchema` property can contain [a complete JSON schema](https://json-schema.org/) to validate the content of the variable. -Trying to set a variable to a value that is not compatible with the schema will fail. +Out of the box, you cannot edit *any* variable. Variables MUST be declared in the map. +Check the [dedicated variables page](variables.md) to learn how to declare a variable in a map. ## Tracking variables changes diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md new file mode 100644 index 00000000..bd194138 --- /dev/null +++ b/docs/maps/entry-exit.md @@ -0,0 +1,67 @@ +{.section-title.accent.text-primary} +# Entries and exits + +https://www.youtube.com/watch?v=MuhVgu8H7U0 + +## Defining a default entry point + +In order to define a default start position, you MUST create a layer named "`start`" on your map. This layer MUST contain at least one tile. The players will start on the tile of this layer. If the layer contains many tiles, the players will start randomly on one of those tiles. + +![Start layer screenshot](images/start_layer.png) + +In the screenshot above, the start layer is made of the 2 white tiles. These tiles are not visible to the end user because they are hidden below the "bottom" layer that displays the floor of the map. + +{.alert.alert-info} +**Pro tip**: if you expect many people to connect to your map at the same time (for instance, if you are organizing a big event), consider making a large start zone. This way, users will not all appear at the same position and will not pop randomly in a chat with someone connecting at the same moment. + +## Defining exits + +In order to place an exit on your scene that leads to another scene: + +* You must create a specific layer. When a character reaches ANY tile of that layer, it will exit the scene. +* In layer properties, you MUST add "`exitUrl`" property. It represents the URL of the next scene. You can put relative or absolute URLs. +* If you want to have multiple exits, you can create many layers. Each layer has a different key `exitUrl` and has tiles that represent exits to another scene. + +![](images/exit_layer_map.png) + +{.alert.alert-warning} +**Note:** in older releases of WorkAdventure, you could link to a map file directly using properties `exitSceneUrl` and `exitInstance`. Those properties are now **deprecated**. Use "`exitUrl`" instead. + +## Understanding map URLs in WorkAdventure + +There are 2 kinds of URLs in WorkAdventure: + +* Public URLs are in the form `https://play.workadventu.re/_/[instance]/[server]/[path to map]` +* Private URLs (used in paid accounts) are in the form `https://play.workadventu.re/@/[organization]/[world]/[map]` + +Assuming your JSON map is hosted at "`https://example.com/my/map.json`", then you can browse your map at "`https://play.workadventu.re/_/global/example.com/my/map.json`". Here, "global" is a name of an "instance" of your map. You can put anything instead of "global" here. People on the same instance of the map can see each others. If 2 users use 2 different instances, they are on the same map, but in 2 parallel universes. They cannot see each other. + +## Defining several entry points + +Often your map will have several exits, and therefore, several entry points. For instance, if there is an exit by a door that leads to the garden map, when you come back from the garden you expect to come back by the same door. Therefore, a map can have several entry points. Those entry points are "named" (they have a name). + +In order to create a named entry point: + +You can create a new layer for your entry point or use an existing layer with named tiles. + +* If you don't use the layer named "`start`", you MUST add a boolean "`startLayer`" property to the layer properties. It MUST be set to true. +* If you use this method, when a character enters the map by this entry point, it will enter randomly on ANY tile of that layer. The name of the entry point is the name of that layer. + +![](images/layer-entry-point.png) + +You can also use the tiles properties to create entry point. + +* To do that, you will need to have a layer named "`start`" or with the "`startLayer`" property. Then you MUST add a string "`start`" property to a tile than you use in that layer. The name of the entry point is the value that property. +* If you use this method, when a character enters the map by this entry point, it will enter on ANY tile of the same kind in that layer. + +![](images/tile-entry-point.png) + +Notes : + +* Two tiles with a string "start" property with different value can be in the same layer of entries. +* A tile with a string "start" property that is not in a layer of entries won't usable as an entry point. + +How to use entry point : + +* To enter via this entry point, simply add a hash with the entry point name to the URL ("#[_entryPointName_]"). For instance: "`https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point`". +* You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL) diff --git a/docs/maps/hosting.md b/docs/maps/hosting.md new file mode 100644 index 00000000..cd3d310d --- /dev/null +++ b/docs/maps/hosting.md @@ -0,0 +1,25 @@ +{.section-title.accent.text-primary} +# Hosting your map + +The [Getting Started](.) page proposes to use a "starter kit" that is relying on GitHub pages for hosting the map. This is a fairly good solution as GitHub pages offers a free and performant hosting. + +But using GitHub pages is not necessary. You can host your maps on any webserver. + +{.alert.alert-warning} +If you decide to host your maps on your own webserver, you must **configure CORS headers** in your browser to allow access from WorkAdventure. + +## Configuring CORS headers + +CORS headers ([Cross Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)) are useful when a website want to make some resources accessible to another website. This is exactly what we want to do. We want the map you are designing to be accessible from the WorkAdventure domain (`play.workadventu.re`). + +### Enabling CORS for Apache + +In order to enable CORS in your Apache configuration, you will need to ensure the `headers` module is enabled. + +In your Apache configuration file, simply add the following line inside either the ``, ``, `` or `` sections, or within a `.htaccess` file. + + Header set Access-Control-Allow-Origin "*" + +### Enabling CORS on another webserver + +Check out [enable-cors.org](https://enable-cors.org/server.html) which has detailed instructions on how to enable CORS on many different webservers. diff --git a/docs/maps/images/click_space_jitsi.png b/docs/maps/images/click_space_jitsi.png new file mode 100644 index 00000000..ec7d7198 Binary files /dev/null and b/docs/maps/images/click_space_jitsi.png differ diff --git a/docs/maps/images/click_space_open_website.png b/docs/maps/images/click_space_open_website.png new file mode 100644 index 00000000..09d0daa2 Binary files /dev/null and b/docs/maps/images/click_space_open_website.png differ diff --git a/docs/maps/images/create_repo.png b/docs/maps/images/create_repo.png new file mode 100644 index 00000000..38083edd Binary files /dev/null and b/docs/maps/images/create_repo.png differ diff --git a/docs/maps/images/exit_layer_map.png b/docs/maps/images/exit_layer_map.png new file mode 100644 index 00000000..dd3e32f1 Binary files /dev/null and b/docs/maps/images/exit_layer_map.png differ diff --git a/docs/maps/images/github_pages.png b/docs/maps/images/github_pages.png new file mode 100644 index 00000000..7b66c24c Binary files /dev/null and b/docs/maps/images/github_pages.png differ diff --git a/docs/maps/images/layer-entry-point.png b/docs/maps/images/layer-entry-point.png new file mode 100644 index 00000000..52345800 Binary files /dev/null and b/docs/maps/images/layer-entry-point.png differ diff --git a/docs/maps/images/open_website.png b/docs/maps/images/open_website.png new file mode 100644 index 00000000..75023a4e Binary files /dev/null and b/docs/maps/images/open_website.png differ diff --git a/docs/maps/images/open_website_policy.png b/docs/maps/images/open_website_policy.png new file mode 100644 index 00000000..d72e3472 Binary files /dev/null and b/docs/maps/images/open_website_policy.png differ diff --git a/docs/maps/images/start_kit_start_screen.png b/docs/maps/images/start_kit_start_screen.png new file mode 100644 index 00000000..fd1a63f2 Binary files /dev/null and b/docs/maps/images/start_kit_start_screen.png differ diff --git a/docs/maps/images/start_layer.png b/docs/maps/images/start_layer.png new file mode 100644 index 00000000..07a80dab Binary files /dev/null and b/docs/maps/images/start_layer.png differ diff --git a/docs/maps/images/tile-entry-point.png b/docs/maps/images/tile-entry-point.png new file mode 100644 index 00000000..45c36d2d Binary files /dev/null and b/docs/maps/images/tile-entry-point.png differ diff --git a/docs/maps/images/use_this_template.png b/docs/maps/images/use_this_template.png new file mode 100644 index 00000000..6ed83ab9 Binary files /dev/null and b/docs/maps/images/use_this_template.png differ diff --git a/docs/maps/images/website_address.png b/docs/maps/images/website_address.png new file mode 100644 index 00000000..d994e9f5 Binary files /dev/null and b/docs/maps/images/website_address.png differ diff --git a/docs/maps/images/youtube.jpg b/docs/maps/images/youtube.jpg new file mode 100644 index 00000000..3dc6c15b Binary files /dev/null and b/docs/maps/images/youtube.jpg differ diff --git a/docs/maps/index.md b/docs/maps/index.md new file mode 100644 index 00000000..7f5e7867 --- /dev/null +++ b/docs/maps/index.md @@ -0,0 +1,128 @@ +{.section-title.accent.text-primary} +# Create your map + +## Tools you will need + +In order to build your own map for WorkAdventure, you need: + +* the [Tiled editor](https://www.mapeditor.org/) software +* "tiles" (i.e. images) to create your map +* a web-server to serve your map + +WorkAdventure comes with a "map starter kit" that we recommend using to start designing your map quickly. It contains **a good default tileset** for building an office and it proposes to **use Github static pages as a web-server** which is both free and performant. It also comes with a local webserver for testing purpose and with Typescript support (if you are looking to use the [map scripting API]({{url('/map-building/scripting')}}). + +{.alert.alert-info} +If you are looking to host your maps on your own webserver, be sure to read the [Self-hosting your map](hosting.md) guide. + +[](https://www.youtube.com/watch?v=lu1IZgBJJD4) + +## Getting started + +Start by [creating a GitHub account](https://github.com/join) if you don't already have one. + +Then, go to the [Github map starter kit repository page](https://github.com/thecodingmachine/workadventure-map-starter-kit) and click the **"Use this template"** button. + +
+ +
The "Use this template" button
+
+ +You will be prompted to enter a repository name for your map. + +
+ +
The "create a new repository" page
+
+ +**Make sure to check the "Include all branches" checkbox, otherwise the Github Pages deployment process will not be setup automatically.** + +If you miss that step, don't worry, you can always fix that by clicking on the **Settings tab** of your repository and scroll down to the **GitHub Pages** section. Then select the **gh-pages** branch. It might already be selected, but please be sure to click on it nonetheless (otherwise GitHub will not enable GitHub pages). + +{.alert.alert-info} +If you only see a "master" branch and if the **gh-pages** branch does not appear here, simply wait a few minutes and refresh your page. When you created the project, Github Actions triggered a job that is in charge of creating the **gh-pages** branch. Maybe the job is not finished yet. + +
+ +
The GitHub pages configuration section
+
+ +Wait a few minutes... Github will deploy a new website with the content of the repository. The address of the website is visible in the "GitHub Pages" section. + +
+ +
Your website is ready!
+
+ +Click on the link. You should be redirected directly to WorkAdventure, on your map! + +## Customizing your map + +Your map is now up and online, but this is still the demo map from the starter kit. You need to customize it. + +### Cloning the map + +Start by cloning the map. If you are used to Git and GitHub, simply clone the map to your computer using your preferred tool and [jump to the next chapter](#loading-the-map-in-tiled). + +If you are new to Git, cloning the map means downloading the map to your computer. To do this, you will need Git, or a Git compatible tool. Our advice is to use [GitHub Desktop](https://desktop.github.com/). We recommend you take some time mastering the notion of pull / commit / push as this will make uploading your maps really easier. + +As an (easier) alternative, you can simply use the "Export" button to download the code of the map in a big Zip file. When you want to upload your work again, you will simply drag'n'drop your files in the GitHub website. + +### Loading the map in Tiled + +The sample map is in the file `map.json`. You can load this file in [Tiled](https://www.mapeditor.org/). + +Now, it's up to you to edit the map and write your own map. + +Some resources regarding Tiled: + +* [Tiled documentation](https://doc.mapeditor.org/en/stable/manual/introduction/) +* [Tiled video tutorials](https://www.gamefromscratch.com/post/2015/10/14/Tiled-Map-Editor-Tutorial-Series.aspx) + +### Testing your map locally + +In order to test your map, you need a webserver to host your map. The "map starter kit" comes with a local webserver that you can use to test your map. + +In order to start the webserver, you will need [Node.JS](https://nodejs.org/en/). When it is downloaded, open your command line and from the directory of the map, run this command: + + $ npm install + +This will install the local webserver. + + $ npm run start + +This command will start the webserver and open the welcome page. You should see a page looking like this: + +
+ +
The welcome page of the "map start kit"
+
+ +From here, you simply need to click the "Test this map" button to test your map in WorkAdventure. + +{.alert.alert-warning} +The local web server can only be used to test your map locally. In particular, the link will only work on your computer. You cannot share it with other people. + +### Pushing the map + +When your changes are ready, you need to "commit" and "push" (i.e. "upload") the changes back to GitHub. Just wait a few minutes, and your map will be propagated automatically to the GitHub pages web-server. + +## Testing your map + +To test your map, you need to find its URL. There are 2 kinds of URLs in WorkAdventure: + +* Test URLs are in the form `https://play.workadventu.re/_/[instance]/[server]/[path to map]` +* Registered URLs are in the form `https://play.workadventu.re/@/[organization]/[world]/[map]` + +Assuming your JSON map is hosted at "`https://myuser.github.io/myrepo/map.json`", then you can browse your map at "`https://play.workadventu.re/_/global/myuser.github.io/myrepo/map.json`". Here, "global" is a name of an "instance" of your map. You can put anything instead of "global" here. People on the same instance of the map can see each others. If 2 users use 2 different instances, they are on the same map, but in 2 parallel universes. They cannot see each other. + +This will connect you to a "public" instance. Anyone can come and connect to a public instance. If you want to manage invitations, or to perform some moderation, you will need to create a "private" instance. Private instances are available in "pro" accounts. + +
+
+

Need some help?

+

WorkAdventure is a constantly evolving project and there is plenty of room for improvement regarding map editing.

+

If you are facing any troubles, do not hesitate to open an "issue" in the + GitHub WorkAdventure account. +

+
+
diff --git a/docs/maps/meeting-rooms.md b/docs/maps/meeting-rooms.md new file mode 100644 index 00000000..97d1b96f --- /dev/null +++ b/docs/maps/meeting-rooms.md @@ -0,0 +1,81 @@ +{.section-title.accent.text-primary} +# Meeting rooms + +https://www.youtube.com/watch?v=cN9VMWHN0eo + +## Opening a Jitsi meet when walking on the map + +On your map, you can define special zones (meeting rooms) that will trigger the opening of a Jitsi meet. When a player will pass over these zones, a Jitsi meet will open (as an iframe on the right side of the screen) + +In order to create Jitsi meet zones: + +* You must create a specific layer. +* In layer properties, you MUST add a "`jitsiRoom`" property (of type "`string`"). The value of the property is the name of the room in Jitsi. Note: the name of the room will be "slugified" and prepended with the name of the instance of the map (so that different instances of the map have different rooms) + +## Triggering of the "Jitsi meet" action + +By default, Jitsi meet will open when a user enters the zone defined on the map. + +It is however possible to trigger Jitsi only on user action. You can do this with the `jitsiTrigger` property. + +If you set `jitsiTrigger: onaction`, when the user walks on the layer, an alert message will be displayed at the bottom of the screen: + +
+ +
Jitsi meet will only open if the user clicks Space
+
+ +If you set `jitsiTriggerMessage: your message action` you can edit alert message displayed. If is not defined, the default message displayed is 'Press on SPACE to enter in jitsi meet room'. + +## Customizing your "Jitsi meet" + +Your Jitsi meet experience can be customized using Jitsi specific config options. The `jitsiConfig` and `jitsiInterfaceConfig` properties can be used on the Jitsi layer to change the way Jitsi looks and behaves. Those 2 properties are accepting a JSON string. + +For instance, use `jitsiConfig: { "startWithAudioMuted": true }` to automatically mute the microphone when someone enters a room. Or use `jitsiInterfaceConfig: { "DEFAULT_BACKGROUND": "#77ee77" }` to change the background color of Jitsi. + +The `jitsiConfig` property will override the Jitsi [config.js](https://github.com/jitsi/jitsi-meet/blob/master/config.js) file + +The `jitsiInterfaceConfig` property will override the Jitsi [interface_config.js](https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js) file + +
If your customizations are not working: +
    +
  • First, check that the JSON you are entering is valid. Take a look at the console in your browser. If the JSON string is invalid, you should see a warning.
  • +
  • Then, check that the JSON you are using is matching the version of Jitsi used.
  • +
+
+ +## Granting moderator controls in Jitsi + +{.alert.alert-info} +Moderator controls are linked to member tags. You need a pro account to edit member tags. + +You can grant moderator rights to some of your members. Jitsi moderators can: + +* Publish a Jitsi meeting on Youtube Live (you will need a Youtube Live account) +* Record a meeting to Dropbox (you will need a Dropbox account) +* Mute someone +* Mute everybody expect one speaker +* Kick users out of the meeting + +In order to grant moderator rights to a given user, you can add a `jitsiRoomAdminTag` property to your Jitsi layer. For instance, if you write a property: + + jitsiRoomAdminTag: speaker + +then, any of your member with the `speaker` tag will be automatically granted moderator rights over this Jitsi instance. + +You can read more about [managing member tags in the admin documentation](/admin-guide/manage-members). + +## Using another Jitsi server + +WorkAdventure usually comes with a default Jitsi meet installation. If you are using the online version at `workadventu.re`, we are handling a Jitsi meet cluster for you. If you are running the self-hosted version of WorkAdventure, the administrator probably set up a Jitsi meet instance too. + +You have the possibility, in your map, to override the Jitsi meet instance that will be used by default. This can be useful for regulatory reasons. Maybe your company wants to keep control on the video streams and therefore, wants to self-host a Jitsi instance? Or maybe you want to use a very special configuration or very special version of Jitsi? + +Use the `jitsiUrl` property to in the Jitsi layer to specify the Jitsi instance that should be used. Beware, `jitsiUrl` takes in parameter a **domain name**, without the protocol. So you should use: +`jitsiUrl: meet.jit.si` +and not +`jitsiUrl: https://meet.jit.si` + +{.alert.alert-info} +When you use `jitsiUrl`, the targeted Jitsi instance must be public. You cannot use moderation features or the JWT +tokens authentication with maps configured using the `jitsiUrl` property. diff --git a/docs/maps/menu.php b/docs/maps/menu.php new file mode 100644 index 00000000..9fb3428f --- /dev/null +++ b/docs/maps/menu.php @@ -0,0 +1,133 @@ + 'Getting started', + 'url' => '/map-building', + 'markdown' => 'maps.index' + ], + [ + 'title' => 'WorkAdventure maps', + 'url' => '/map-building/wa-maps', + 'markdown' => 'maps.wa-maps' + ], + [ + 'title' => 'Entries and exits', + 'url' => '/map-building/entry-exit.md', + 'markdown' => 'maps.entry-exit' + ], + [ + 'title' => 'Opening a website', + 'url' => '/map-building/opening-a-website.md', + 'markdown' => 'maps.opening-a-website' + ], + [ + 'title' => 'Meeting rooms', + 'url' => '/map-building/meeting-rooms.md', + 'markdown' => 'maps.meeting-rooms' + ], + [ + 'title' => 'Special zones', + 'url' => '/map-building/special-zones.md', + 'markdown' => 'maps.special-zones' + ], + [ + 'title' => 'Animations', + 'url' => '/map-building/animations.md', + 'markdown' => 'maps.animations' + ], + [ + 'title' => 'Integrated websites', + 'url' => '/map-building/website-in-map.md', + 'markdown' => 'maps.website-in-map' + ], + [ + 'title' => 'Variables', + 'url' => '/map-building/variables.md', + 'markdown' => 'maps.variables' + ], + [ + 'title' => 'Self-hosting your map', + 'url' => '/map-building/hosting.md', + 'markdown' => 'maps.hosting' + ], + $extraMenu, + [ + 'title' => 'Scripting maps', + 'url' => '/map-building/scripting', + 'markdown' => 'maps.scripting', + 'children' => [ + [ + 'title' => 'Using Typescript', + 'url' => '/map-building/using-typescript.md', + 'markdown' => 'maps.using-typescript' + ], + [ + 'title' => 'API Reference', + 'url' => '/map-building/api-reference', + 'markdown' => 'maps.api-reference', + 'collapse' => true, + 'children' => [ + [ + 'title' => 'Initialization', + 'url' => '/map-building/api-start.md', + 'markdown' => 'maps.api-start', + ], + [ + 'title' => 'Navigation', + 'url' => '/map-building/api-nav.md', + 'markdown' => 'maps.api-nav', + ], + [ + 'title' => 'Chat', + 'url' => '/map-building/api-chat.md', + 'markdown' => 'maps.api-chat', + ], + [ + 'title' => 'Room', + 'url' => '/map-building/api-room.md', + 'markdown' => 'maps.api-room', + ], + [ + 'title' => 'State', + 'url' => '/map-building/api-state.md', + 'markdown' => 'maps.api-state', + ], + [ + 'title' => 'Player', + 'url' => '/map-building/api-player.md', + 'markdown' => 'maps.api-player', + ], + [ + 'title' => 'UI', + 'url' => '/map-building/api-ui.md', + 'markdown' => 'maps.api-ui', + ], + [ + 'title' => 'Sound', + 'url' => '/map-building/api-sound.md', + 'markdown' => 'maps.api-sound', + ], + [ + 'title' => 'Controls', + 'url' => '/map-building/api-controls.md', + 'markdown' => 'maps.api-controls', + ], + [ + 'title' => 'Deprecated', + 'url' => '/map-building/api-deprecated.md', + 'markdown' => 'maps.api-deprecated', + ], + ] + ], + $extraUtilsMenu + ] + ], + [ + 'title' => 'Troubleshooting', + 'url' => '/map-building/troubleshooting', + 'view' => 'content.map.troubleshooting' + ], +]; diff --git a/docs/maps/opening-a-website.md b/docs/maps/opening-a-website.md new file mode 100644 index 00000000..682306b4 --- /dev/null +++ b/docs/maps/opening-a-website.md @@ -0,0 +1,66 @@ +{.section-title.accent.text-primary} +# Opening a website when walking on the map + +https://www.youtube.com/watch?v=Me8cu5lLN3A + +## The openWebsite property + +On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe +on the right side of the screen) + +In order to create a zone that opens websites: + +* You must create a specific layer. +* In layer properties, you MUST add a "`openWebsite`" property (of type "`string`"). The value of the property is the URL of the website to open (the URL must start with "https://") +* You may also use "`openWebsiteWidth`" property (of type "`number`" between 0 and 100) to control the width of the iframe. +* You may also use "`openTab`" property (of type "`string`") to open in a new tab instead. + +{.alert.alert-warning} +A website can explicitly forbid another website from loading it in an iFrame using +the [X-Frame-Options HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options). + +## Integrating a Youtube video + +A common use case is to use `openWebsite` to open a Youtube video. + +The global Youtube page cannot be embedded into an iFrame (it has the `X-Frame-Options` HTTP header). + +To embed a Youtube video, be sure to **use the "embed" link**. You can get this link be clicking "Share > Embed" in Youtube. + +
+ +
Find the URL of your video in the "embed Video" HTML snippet on Youtube
+
+ +
+ +
Put this URL in the "openWebsite" property
+
+ +### Triggering of the "open website" action + +By default, the iFrame will open when a user enters the zone defined on the map. + +It is however possible to trigger the iFrame only on user action. You can do this with the `openWebsiteTrigger` property. + +If you set `openWebsiteTrigger: onaction`, when the user walks on the layer, an alert message will be displayed at the bottom of the screen: + +
+ +
The iFrame will only open if the user clicks Space
+
+ +If you set `openWebsiteTriggerMessage: your message action` you can edit alert message displayed. If is not defined, the default message displayed is 'Press on SPACE to open the web site'. + +### Setting the iFrame "allow" attribute + +By default, iFrames have limited rights in browsers. For instance, they cannot put their content in fullscreen, they cannot start your webcam, etc... + +If you want to grant additional access rights to your iFrame, you should use the `openWebsitePolicy` property. The value of this property will be directly used for the [`allow` atttribute of your iFrame](https://developer.mozilla.org/en-US/docs/Web/HTTP/Feature_Policy/Using_Feature_Policy#the_iframe_allow_attribute). + +For instance, if you want an iFrame to be able to go in fullscreen, you will use the property `openWebsitePolicy: fullscreen` + +
+ +
The generated iFrame will have the allow attribute set to: <iframe allow="fullscreen">
+
diff --git a/docs/maps/special-zones.md b/docs/maps/special-zones.md new file mode 100644 index 00000000..fff4e730 --- /dev/null +++ b/docs/maps/special-zones.md @@ -0,0 +1,27 @@ +{.section-title.accent.text-primary} +# Other special zones + +## Making a "silent" zone + +https://www.youtube.com/watch?v=z7XLo06o-ow + +On your map, you can define special silent zones where nobody is allowed to talk. In these zones, users will not speak to each others, even if they are next to each others. + +In order to create a silent zone: + +* You must create a specific layer. +* In layer properties, you MUST add a boolean "`silent`" property. If the silent property is checked, the users are entering the silent zone when they walk on any tile of the layer. + +## Playing sounds or background music + +Your map can define special zones where a sound or background music will automatically be played. + +In order to create a zone that triggers sounds/music: + +* You must create a specific layer. +* In layer properties, you MUST add a "`playAudio`" property. The value of the property is a URL to an MP3 file that will be played. The URL can be relative to the URL of the map. +* You may use the boolean property "`audioLoop`" to make the sound loop (thanks captain obvious). +* If the "`audioVolume`" property is set, the audio player uses either the value of the property or the last volume set by the user - whichever is smaller. This property is a float from 0 to 1.0 + +{.alert.alert-info} +"`playAudioLoop`" is deprecated and should not be used anymore. diff --git a/docs/maps/using-typescript.md b/docs/maps/using-typescript.md new file mode 100644 index 00000000..04fcc171 --- /dev/null +++ b/docs/maps/using-typescript.md @@ -0,0 +1,183 @@ +{.section-title.accent.text-primary} +# Using Typescript with the scripting API + +{.alert.alert-info} +The easiest way to get started with writing scripts in Typescript is to use the +[Github map starter kit repository](https://github.com/thecodingmachine/workadventure-map-starter-kit). It comes with +Typescript enabled. If you are **not** using the "map starter kit", this page explains how to add Typescript to your +own scripts. + +## The short story + +In this page, we will assume you already know Typescript and know how to set it up with Webpack. + +To work with the scripting API in Typescript, you will need the typings of the `WA` object. These typings can be downloaded from the `@workadventure/iframe-api-typings` package. + +```console +$ npm install --save-dev @workadventure/iframe-api-typings +``` + +Furthermore, you need to make the global `WA` object available. To do this, edit the entry point of your project (usually, it is a file called `index.ts` in the root directory). + +Add this line at the top of the file: + +**index.ts** +```typescript +/// +``` + +From there, you should be able to use Typescript in your project. + +## The long story + +Below is a step by step guide explaining how to set up Typescript + Webpack along your WorkAdventure map. + +In your map directory, start by adding a `package.json` file. This file will contain dependencies on Webpack, Typescript and the Workadventure typings: + +**package.json** +```json +{ + "devDependencies": { + "@workadventure/iframe-api-typings": "^1.2.1", + "eslint": "^7.24.0", + "html-webpack-plugin": "^5.3.1", + "ts-loader": "^8.1.0", + "ts-node": "^9.1.1", + "typescript": "^4.2.4", + "webpack": "^5.31.2", + "webpack-cli": "^4.6.0", + "webpack-dev-server": "^3.11.2", + "webpack-merge": "^5.7.3" + }, + "scripts": { + "start": "webpack serve --open", + "build": "webpack --config webpack.prod.js", + } +} +``` + +You can now install the dependencies: + +```console +$ npm install +``` + +We now need to add a Webpack configuration file (for development mode). This Webpack file will: + +* Start a local webserver that will be in charge of serving the map +* Compile Typescript into Javascript and serve it automatically + +**webpack.config.js** +```js +const path = require('path'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + mode: 'development', + entry: './src/index.ts', + devtool: 'inline-source-map', + devServer: { + // The test webserver serves static files from the root directory. + // It comes with CORS enabled (important for WorkAdventure to be able to load the map) + static: { + directory: ".", + serveIndex: true, + watch: true, + }, + host: 'localhost', + allowedHosts: "all", + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", + "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization" + } + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [ '.tsx', '.ts', '.js' ], + }, + output: { + filename: 'script.js', + path: path.resolve(__dirname, 'dist'), + publicPath: '/' + } +}; +``` + +We need to configure Typescript, using a `tsconfig.json` file. + +**tsconfig.json** +```json +{ + "compilerOptions": { + "outDir": "./dist/", + "sourceMap": true, + "moduleResolution": "node", + "module": "CommonJS", + "target": "ES2015", + "declaration": false, + "downlevelIteration": true, + "jsx": "react", + "allowJs": true, + "strict": true + } +} +``` + +Create your entry point (the Typescript file at the root of your project). + +**src/index.ts** +```typescript +/// + +console.log('Hello world!'); +``` + +The first comment line is important in order to get `WA` typings. + +Now, you can start Webpack in dev mode! + +```console +$ npm run start +``` + +This will automatically compile Typescript, and serve it (along the map) on your local webserver (so at `http://localhost:8080/script.js`). Please note that the `script.js` file is never written to the disk. So do not worry if you don't see it appearing, you need to "build" it to actually write it to the disk. + +Final step, you must reference the script inside your map, by adding a `script` property at the root of your map: + +
+ +
The script property
+
+ +### Building the final script + +We now have a correct development setup. But we still need to be able to build the production script from Typescript files. We are not going to use the development server in production. To do this, we will add an additional `webpack.prod.js` file. + +**webpack.prod.js** +```javascript +const { merge } = require('webpack-merge'); +const common = require('./webpack.config.js'); + +module.exports = merge(common, { + mode: 'production', + devtool: 'source-map' +}); +``` + +This file will simply switch the Webpack config file in "production" mode. You can simply run: + +```console +$ npm run build +``` + +and the `script.js` file will be generated in the `dist/` folder. Beware, you will need to move it at the root of map for it to be read by the map. diff --git a/docs/maps/variables.md b/docs/maps/variables.md new file mode 100644 index 00000000..17e803d9 --- /dev/null +++ b/docs/maps/variables.md @@ -0,0 +1,59 @@ +{.section-title.accent.text-primary} +# Variables + +Maps can contain **variables**. Variables are piece of information that store some data. In computer science, we like +to say variables are storing the "state" of the room. + +- Variables are shared amongst all players in a given room. When the value of a variable changes for one player, it changes + for everyone. +- Variables are **invisible**. There are plenty of ways they can act on the room, but by default, you don't see them. + +## Declaring a variable + +In order to declare allowed variables in a room, you need to add **objects** in an "object layer" of the map. + +Each object will represent a variable. + +
+
+ +
+
+ +The name of the variable is the name of the object. +The object **type** MUST be **variable**. + +You can set a default value for the object in the `default` property. + +## Persisting variables state + +Use the `persist` property to save the state of the variable in database. If `persist` is false, the variable will stay +in the memory of the WorkAdventure servers but will be wiped out of the memory as soon as the room is empty (or if the +server restarts). + +{.alert.alert-info} +Do not use `persist` for highly dynamic values that have a short life spawn. + +## Managing access rights to variables + +With `readableBy` and `writableBy`, you control who can read of write in this variable. The property accepts a string +representing a "tag". Anyone having this "tag" can read/write in the variable. + +{.alert.alert-warning} +`readableBy` and `writableBy` are specific to the "online" version of WorkAdventure because the notion of tags +is not available unless you have an "admin" server (that is not part of the self-hosted version of WorkAdventure). + +In a future release, the `jsonSchema` property will contain [a complete JSON schema](https://json-schema.org/) to validate the content of the variable. +Trying to set a variable to a value that is not compatible with the schema will fail. + +## Using variables + +There are plenty of ways to use variables in WorkAdventure: + +- Using the [scripting API](api-state.md), you can read, edit or track the content of variables. +- Using the [Action zones](https://workadventu.re/map-building-extra/generic-action-zones.md), you can set the value of a variable when someone is entering or leaving a zone +- By [binding variable values to properties in the map](https://workadventu.re/map-building-extra/variable-to-property-binding.md) +- By [using automatically generated configuration screens](https://workadventu.re/map-building-extra/automatic-configuration.md) to create forms to edit the value of variables + +In general, variables can be used by third party libraries that you can embed in your map to add extra features. +A good example of such a library is the ["Scripting API Extra" library](https://workadventu.re/map-building-extra/about.md) diff --git a/front/src/Api/Events/ButtonClickedEvent.ts b/front/src/Api/Events/ButtonClickedEvent.ts index de807037..26a8aceb 100644 --- a/front/src/Api/Events/ButtonClickedEvent.ts +++ b/front/src/Api/Events/ButtonClickedEvent.ts @@ -1,10 +1,11 @@ import * as tg from "generic-type-guard"; -export const isButtonClickedEvent = - new tg.IsInterface().withProperties({ +export const isButtonClickedEvent = new tg.IsInterface() + .withProperties({ popupId: tg.isNumber, buttonId: tg.isNumber, - }).get(); + }) + .get(); /** * A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property. */ diff --git a/front/src/Api/Events/ChatEvent.ts b/front/src/Api/Events/ChatEvent.ts index 5729a120..984859e8 100644 --- a/front/src/Api/Events/ChatEvent.ts +++ b/front/src/Api/Events/ChatEvent.ts @@ -1,10 +1,11 @@ import * as tg from "generic-type-guard"; -export const isChatEvent = - new tg.IsInterface().withProperties({ +export const isChatEvent = new tg.IsInterface() + .withProperties({ message: tg.isString, author: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. */ diff --git a/front/src/Api/Events/ClosePopupEvent.ts b/front/src/Api/Events/ClosePopupEvent.ts index 83b09c96..f604a404 100644 --- a/front/src/Api/Events/ClosePopupEvent.ts +++ b/front/src/Api/Events/ClosePopupEvent.ts @@ -1,9 +1,10 @@ import * as tg from "generic-type-guard"; -export const isClosePopupEvent = - new tg.IsInterface().withProperties({ +export const isClosePopupEvent = new tg.IsInterface() + .withProperties({ popupId: tg.isNumber, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/EnterLeaveEvent.ts b/front/src/Api/Events/EnterLeaveEvent.ts index 0c0cb4ff..ca68136e 100644 --- a/front/src/Api/Events/EnterLeaveEvent.ts +++ b/front/src/Api/Events/EnterLeaveEvent.ts @@ -1,9 +1,10 @@ import * as tg from "generic-type-guard"; -export const isEnterLeaveEvent = - new tg.IsInterface().withProperties({ +export const isEnterLeaveEvent = new tg.IsInterface() + .withProperties({ name: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property. */ diff --git a/front/src/Api/Events/GoToPageEvent.ts b/front/src/Api/Events/GoToPageEvent.ts index cb258b03..d8d6467d 100644 --- a/front/src/Api/Events/GoToPageEvent.ts +++ b/front/src/Api/Events/GoToPageEvent.ts @@ -1,11 +1,10 @@ import * as tg from "generic-type-guard"; - - -export const isGoToPageEvent = - new tg.IsInterface().withProperties({ +export const isGoToPageEvent = new tg.IsInterface() + .withProperties({ url: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/LoadSoundEvent.ts b/front/src/Api/Events/LoadSoundEvent.ts index 19b4b8e1..f48f202f 100644 --- a/front/src/Api/Events/LoadSoundEvent.ts +++ b/front/src/Api/Events/LoadSoundEvent.ts @@ -1,9 +1,10 @@ import * as tg from "generic-type-guard"; -export const isLoadSoundEvent = - new tg.IsInterface().withProperties({ +export const isLoadSoundEvent = new tg.IsInterface() + .withProperties({ url: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/OpenPopupEvent.ts b/front/src/Api/Events/OpenPopupEvent.ts index 094ba555..c1070bbe 100644 --- a/front/src/Api/Events/OpenPopupEvent.ts +++ b/front/src/Api/Events/OpenPopupEvent.ts @@ -1,18 +1,20 @@ import * as tg from "generic-type-guard"; -const isButtonDescriptor = - new tg.IsInterface().withProperties({ +const isButtonDescriptor = new tg.IsInterface() + .withProperties({ label: tg.isString, - className: tg.isOptional(tg.isString) - }).get(); + className: tg.isOptional(tg.isString), + }) + .get(); -export const isOpenPopupEvent = - new tg.IsInterface().withProperties({ +export const isOpenPopupEvent = new tg.IsInterface() + .withProperties({ popupId: tg.isNumber, targetObject: tg.isString, message: tg.isString, - buttons: tg.isArray(isButtonDescriptor) - }).get(); + buttons: tg.isArray(isButtonDescriptor), + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/OpenTabEvent.ts b/front/src/Api/Events/OpenTabEvent.ts index e510f8b6..6fe6ec21 100644 --- a/front/src/Api/Events/OpenTabEvent.ts +++ b/front/src/Api/Events/OpenTabEvent.ts @@ -1,11 +1,10 @@ import * as tg from "generic-type-guard"; - - -export const isOpenTabEvent = - new tg.IsInterface().withProperties({ +export const isOpenTabEvent = new tg.IsInterface() + .withProperties({ url: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/PlaySoundEvent.ts b/front/src/Api/Events/PlaySoundEvent.ts index 33ca1ff4..6fe56746 100644 --- a/front/src/Api/Events/PlaySoundEvent.ts +++ b/front/src/Api/Events/PlaySoundEvent.ts @@ -1,22 +1,23 @@ import * as tg from "generic-type-guard"; - -const isSoundConfig = - new tg.IsInterface().withProperties({ +const isSoundConfig = new tg.IsInterface() + .withProperties({ volume: tg.isOptional(tg.isNumber), loop: tg.isOptional(tg.isBoolean), mute: tg.isOptional(tg.isBoolean), rate: tg.isOptional(tg.isNumber), detune: tg.isOptional(tg.isNumber), seek: tg.isOptional(tg.isNumber), - delay: tg.isOptional(tg.isNumber) - }).get(); + delay: tg.isOptional(tg.isNumber), + }) + .get(); -export const isPlaySoundEvent = - new tg.IsInterface().withProperties({ +export const isPlaySoundEvent = new tg.IsInterface() + .withProperties({ url: tg.isString, - config : tg.isOptional(isSoundConfig), - }).get(); + config: tg.isOptional(isSoundConfig), + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/StopSoundEvent.ts b/front/src/Api/Events/StopSoundEvent.ts index 6d12516d..cdfe43ca 100644 --- a/front/src/Api/Events/StopSoundEvent.ts +++ b/front/src/Api/Events/StopSoundEvent.ts @@ -1,9 +1,10 @@ import * as tg from "generic-type-guard"; -export const isStopSoundEvent = - new tg.IsInterface().withProperties({ +export const isStopSoundEvent = new tg.IsInterface() + .withProperties({ url: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/UserInputChatEvent.ts b/front/src/Api/Events/UserInputChatEvent.ts index de21ff6e..9de41327 100644 --- a/front/src/Api/Events/UserInputChatEvent.ts +++ b/front/src/Api/Events/UserInputChatEvent.ts @@ -1,9 +1,10 @@ import * as tg from "generic-type-guard"; -export const isUserInputChatEvent = - new tg.IsInterface().withProperties({ +export const isUserInputChatEvent = new tg.IsInterface() + .withProperties({ message: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the game to the iFrame when a user types a message in the chat. */ diff --git a/front/src/Api/iframe/Ui/ButtonDescriptor.ts b/front/src/Api/iframe/Ui/ButtonDescriptor.ts index 119daf5c..9cf1688a 100644 --- a/front/src/Api/iframe/Ui/ButtonDescriptor.ts +++ b/front/src/Api/iframe/Ui/ButtonDescriptor.ts @@ -1,4 +1,4 @@ -import type {Popup} from "./Popup"; +import type { Popup } from "./Popup"; export type ButtonClickedCallback = (popup: Popup) => void; @@ -6,13 +6,13 @@ export interface ButtonDescriptor { /** * The label of the button */ - label: string, + label: string; /** * The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled" */ - className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled", + className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled"; /** * Callback called if the button is pressed */ - callback: ButtonClickedCallback, + callback: ButtonClickedCallback; } diff --git a/front/src/Api/iframe/Ui/Popup.ts b/front/src/Api/iframe/Ui/Popup.ts index 37dea922..085fdc2c 100644 --- a/front/src/Api/iframe/Ui/Popup.ts +++ b/front/src/Api/iframe/Ui/Popup.ts @@ -1,19 +1,18 @@ -import {sendToWorkadventure} from "../IframeApiContribution"; -import type {ClosePopupEvent} from "../../Events/ClosePopupEvent"; +import { sendToWorkadventure } from "../IframeApiContribution"; +import type { ClosePopupEvent } from "../../Events/ClosePopupEvent"; export class Popup { - constructor(private id: number) { - } + constructor(private id: number) {} /** * Closes the popup */ public close(): void { sendToWorkadventure({ - 'type': 'closePopup', - 'data': { - 'popupId': this.id, - } as ClosePopupEvent + type: "closePopup", + data: { + popupId: this.id, + } as ClosePopupEvent, }); } } diff --git a/front/src/Api/iframe/registeredCallbacks.ts b/front/src/Api/iframe/registeredCallbacks.ts index 5d6f784d..3b6ee6c7 100644 --- a/front/src/Api/iframe/registeredCallbacks.ts +++ b/front/src/Api/iframe/registeredCallbacks.ts @@ -1,16 +1,18 @@ -import type {IframeResponseEventMap} from "../../Api/Events/IframeEvent"; -import type {IframeCallback} from "../../Api/iframe/IframeApiContribution"; -import type {IframeCallbackContribution} from "../../Api/iframe/IframeApiContribution"; +import type { IframeResponseEventMap } from "../../Api/Events/IframeEvent"; +import type { IframeCallback } from "../../Api/iframe/IframeApiContribution"; +import type { IframeCallbackContribution } from "../../Api/iframe/IframeApiContribution"; -export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: IframeCallback } = {} +export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: IframeCallback } = {}; -export function apiCallback(callbackData: IframeCallbackContribution): IframeCallbackContribution { +export function apiCallback( + callbackData: IframeCallbackContribution +): IframeCallbackContribution { const iframeCallback = { typeChecker: callbackData.typeChecker, - callback: callbackData.callback + callback: callbackData.callback, } as IframeCallback; const newCallback = { [callbackData.type]: iframeCallback }; - Object.assign(registeredCallbacks, newCallback) + Object.assign(registeredCallbacks, newCallback); return callbackData as unknown as IframeCallbackContribution; } diff --git a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte index 8f4de785..ed4a84ca 100644 --- a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte +++ b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte @@ -3,10 +3,11 @@ import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore"; import firefoxImg from "./images/help-setting-camera-permission-firefox.png"; import chromeImg from "./images/help-setting-camera-permission-chrome.png"; + import {getNavigatorType, isAndroid as isAndroidFct, NavigatorType} from "../../WebRtc/DeviceUtils"; - let isAndroid = window.navigator.userAgent.includes('Android'); - let isFirefox = window.navigator.userAgent.includes('Firefox'); - let isChrome = window.navigator.userAgent.includes('Chrome'); + let isAndroid = isAndroidFct(); + let isFirefox = getNavigatorType() === NavigatorType.firefox; + let isChrome = getNavigatorType() === NavigatorType.chrome; function refresh() { window.location.reload(); diff --git a/front/src/Components/Menu/Menu.svelte b/front/src/Components/Menu/Menu.svelte index 4086a9ae..6cbef9c1 100644 --- a/front/src/Components/Menu/Menu.svelte +++ b/front/src/Components/Menu/Menu.svelte @@ -7,7 +7,13 @@ import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte"; import ContactSubMenu from "./ContactSubMenu.svelte"; import CustomSubMenu from "./CustomSubMenu.svelte" - import {customMenuIframe, menuVisiblilityStore, SubMenusInterface, subMenusStore} from "../../Stores/MenuStore"; + import { + checkSubMenuToShow, + customMenuIframe, + menuVisiblilityStore, + SubMenusInterface, + subMenusStore + } from "../../Stores/MenuStore"; import {onDestroy, onMount} from "svelte"; import {get} from "svelte/store"; import type {Unsubscriber} from "svelte/store"; @@ -25,6 +31,8 @@ } }) + checkSubMenuToShow(); + switchMenu(SubMenusInterface.settings); }) @@ -95,6 +103,7 @@ @@ -110,9 +119,9 @@ font-family: "Press Start 2P"; pointer-events: auto; - height: 80vh; - width: 75vw; - top: 10vh; + height: 80%; + width: 75%; + top: 10%; position: relative; margin: auto; @@ -139,16 +148,34 @@ div.menu-submenu-container { background-color: #333333; color: whitesmoke; + + .nes-btn.is-error.close { + position: absolute; + top: -20px; + right: -20px; + } } } @media only screen and (max-width: 800px) { div.menu-container-main { --size-first-columns-grid: 120px; - height: 70vh; + height: 70%; top: 55px; - width: 100vw; + width: 100%; font-size: 0.5em; + + div.menu-nav-sidebar { + overflow-y: auto; + } + + div.menu-submenu-container { + .nes-btn.is-error.close { + position: absolute; + top: -35px; + right: 0; + } + } } } \ No newline at end of file diff --git a/front/src/Components/Menu/ProfileSubMenu.svelte b/front/src/Components/Menu/ProfileSubMenu.svelte index 39214b4f..83ec329c 100644 --- a/front/src/Components/Menu/ProfileSubMenu.svelte +++ b/front/src/Components/Menu/ProfileSubMenu.svelte @@ -52,11 +52,6 @@ enableCameraSceneVisibilityStore.showEnableCameraScene(); gameManager.leaveGame(EnableCameraSceneName,new EnableCameraScene()); } - - //TODO: Uncomment when login will be completely developed - /*function clickLogin() { - connectionManager.loadOpenIDScreen(); - }*/
@@ -71,20 +66,17 @@ {:else}
- Sing in + Sign in
{/if}
- +
-
\ No newline at end of file diff --git a/front/src/Connexion/AdminMessagesService.ts b/front/src/Connexion/AdminMessagesService.ts index 0cf7f95f..0b217760 100644 --- a/front/src/Connexion/AdminMessagesService.ts +++ b/front/src/Connexion/AdminMessagesService.ts @@ -1,15 +1,15 @@ -import {Subject} from "rxjs"; -import type {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb"; +import { Subject } from "rxjs"; +import type { BanUserMessage, SendUserMessage } from "../Messages/generated/messages_pb"; export enum AdminMessageEventTypes { - admin = 'message', - audio = 'audio', - ban = 'ban', - banned = 'banned', + admin = "message", + audio = "audio", + ban = "ban", + banned = "banned", } interface AdminMessageEvent { - type: AdminMessageEventTypes, + type: AdminMessageEventTypes; text: string; //todo add optional properties for other event types } @@ -21,14 +21,14 @@ class AdminMessagesService { public messageStream = this._messageStream.asObservable(); constructor() { - this.messageStream.subscribe((event) => console.log('message', event)) + this.messageStream.subscribe((event) => console.log("message", event)); } - onSendusermessage(message: SendUserMessage|BanUserMessage) { + onSendusermessage(message: SendUserMessage | BanUserMessage) { this._messageStream.next({ type: message.getType() as unknown as AdminMessageEventTypes, text: message.getMessage(), - }) + }); } } diff --git a/front/src/Connexion/EmoteEventStream.ts b/front/src/Connexion/EmoteEventStream.ts index 9a639697..e8d01095 100644 --- a/front/src/Connexion/EmoteEventStream.ts +++ b/front/src/Connexion/EmoteEventStream.ts @@ -1,19 +1,17 @@ -import {Subject} from "rxjs"; +import { Subject } from "rxjs"; interface EmoteEvent { - userId: number, - emoteName: string, + userId: number; + emoteName: string; } class EmoteEventStream { - - private _stream:Subject = new Subject(); + private _stream: Subject = new Subject(); public stream = this._stream.asObservable(); - - fire(userId: number, emoteName:string) { - this._stream.next({userId, emoteName}); + fire(userId: number, emoteName: string) { + this._stream.next({ userId, emoteName }); } } -export const emoteEventStream = new EmoteEventStream(); \ No newline at end of file +export const emoteEventStream = new EmoteEventStream(); diff --git a/front/src/Connexion/WorldFullMessageStream.ts b/front/src/Connexion/WorldFullMessageStream.ts index c9f65d84..01ce6f20 100644 --- a/front/src/Connexion/WorldFullMessageStream.ts +++ b/front/src/Connexion/WorldFullMessageStream.ts @@ -1,14 +1,12 @@ -import {Subject} from "rxjs"; +import { Subject } from "rxjs"; class WorldFullMessageStream { - - private _stream:Subject = new Subject(); + private _stream: Subject = new Subject(); public stream = this._stream.asObservable(); - - onMessage(message? :string) { + onMessage(message?: string) { this._stream.next(message); } } -export const worldFullMessageStream = new WorldFullMessageStream(); \ No newline at end of file +export const worldFullMessageStream = new WorldFullMessageStream(); diff --git a/front/src/Exception/TextureError.ts b/front/src/Exception/TextureError.ts index 39a339f6..4e7e2e80 100644 --- a/front/src/Exception/TextureError.ts +++ b/front/src/Exception/TextureError.ts @@ -1 +1 @@ -export class TextureError extends Error{} \ No newline at end of file +export class TextureError extends Error {} diff --git a/front/src/Logger/MessageUI.ts b/front/src/Logger/MessageUI.ts index 2a581091..d10460bd 100644 --- a/front/src/Logger/MessageUI.ts +++ b/front/src/Logger/MessageUI.ts @@ -1,19 +1,21 @@ export class MessageUI { - - static warningMessage(text: string){ + static warningMessage(text: string) { this.removeMessage(); const body = document.getElementById("body"); - body?.insertAdjacentHTML('afterbegin', ` + body?.insertAdjacentHTML( + "afterbegin", + `
${text}
- `); + ` + ); } - static removeMessage(id : string|null = null) { - if(!id){ + static removeMessage(id: string | null = null) { + if (!id) { const messages = document.getElementsByClassName("message-info"); - for (let i = 0; i < messages.length; i++){ + for (let i = 0; i < messages.length; i++) { messages.item(i)?.remove(); } return; diff --git a/front/src/Network/ProtobufClientUtils.ts b/front/src/Network/ProtobufClientUtils.ts index 3d2d5c32..9ba0f40b 100644 --- a/front/src/Network/ProtobufClientUtils.ts +++ b/front/src/Network/ProtobufClientUtils.ts @@ -1,23 +1,22 @@ -import {PositionMessage} from "../Messages/generated/messages_pb"; +import { PositionMessage } from "../Messages/generated/messages_pb"; import Direction = PositionMessage.Direction; -import type {PointInterface} from "../Connexion/ConnexionModels"; +import type { PointInterface } from "../Connexion/ConnexionModels"; export class ProtobufClientUtils { - public static toPointInterface(position: PositionMessage): PointInterface { let direction: string; switch (position.getDirection()) { case Direction.UP: - direction = 'up'; + direction = "up"; break; case Direction.DOWN: - direction = 'down'; + direction = "down"; break; case Direction.LEFT: - direction = 'left'; + direction = "left"; break; case Direction.RIGHT: - direction = 'right'; + direction = "right"; break; default: throw new Error("Unexpected direction"); diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 1c43f452..75eb844f 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -16,7 +16,7 @@ export class Companion extends Container { private delta: number; private invisible: boolean; private updateListener: Function; - private target: { x: number, y: number, direction: PlayerAnimationDirections }; + private target: { x: number; y: number; direction: PlayerAnimationDirections }; private companionName: string; private direction: PlayerAnimationDirections; @@ -36,10 +36,10 @@ export class Companion extends Container { this.companionName = name; - texturePromise.then(resource => { + texturePromise.then((resource) => { this.addResource(resource); this.invisible = false; - }) + }); this.scene.physics.world.enableBody(this); @@ -52,7 +52,7 @@ export class Companion extends Container { this.setDepth(-1); this.updateListener = this.step.bind(this); - this.scene.events.addListener('update', this.updateListener); + this.scene.events.addListener("update", this.updateListener); this.scene.add.existing(this); } @@ -62,7 +62,7 @@ export class Companion extends Container { } public step(time: number, delta: number) { - if (typeof this.target === 'undefined') return; + if (typeof this.target === "undefined") return; this.delta += delta; if (this.delta < 128) { @@ -87,7 +87,10 @@ export class Companion extends Container { const yDir = yDist / Math.max(Math.abs(yDist), 1); const speed = 256; - this.getBody().setVelocity(Math.min(Math.abs(xDist * 2.5), speed) * xDir, Math.min(Math.abs(yDist * 2.5), speed) * yDir); + this.getBody().setVelocity( + Math.min(Math.abs(xDist * 2.5), speed) * xDir, + Math.min(Math.abs(yDist * 2.5), speed) * yDir + ); if (Math.abs(xDist) > Math.abs(yDist)) { if (xDist < 0) { @@ -116,8 +119,8 @@ export class Companion extends Container { y, direction, moving: animationType === PlayerAnimationTypes.Walk, - name: companionName - } + name: companionName, + }; } private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void { @@ -133,7 +136,7 @@ export class Companion extends Container { this.add(sprite); - this.getAnimations(resource).forEach(animation => { + this.getAnimations(resource).forEach((animation) => { this.scene.anims.create(animation); }); @@ -145,60 +148,60 @@ export class Companion extends Container { return [ { key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [1]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [1] }), frameRate: 10, - repeat: 1 + repeat: 1, }, { key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [4]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [4] }), frameRate: 10, - repeat: 1 + repeat: 1, }, { key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [7]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [7] }), frameRate: 10, - repeat: 1 + repeat: 1, }, { key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [10]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [10] }), frameRate: 10, - repeat: 1 + repeat: 1, }, { key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [0, 1, 2]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [0, 1, 2] }), frameRate: 15, - repeat: -1 + repeat: -1, }, { key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [3, 4, 5]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [3, 4, 5] }), frameRate: 15, - repeat: -1 + repeat: -1, }, { key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [6, 7, 8]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [6, 7, 8] }), frameRate: 15, - repeat: -1 + repeat: -1, }, { key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`, - frames: this.scene.anims.generateFrameNumbers(resource, {frames: [9, 10, 11]}), + frames: this.scene.anims.generateFrameNumbers(resource, { frames: [9, 10, 11] }), frameRate: 15, - repeat: -1 - } - ] + repeat: -1, + }, + ]; } private getBody(): Phaser.Physics.Arcade.Body { const body = this.body; if (!(body instanceof Phaser.Physics.Arcade.Body)) { - throw new Error('Container does not have arcade body'); + throw new Error("Container does not have arcade body"); } return body; @@ -212,7 +215,7 @@ export class Companion extends Container { } if (this.scene) { - this.scene.events.removeListener('update', this.updateListener); + this.scene.events.removeListener("update", this.updateListener); } super.destroy(); diff --git a/front/src/Phaser/Companion/CompanionTextures.ts b/front/src/Phaser/Companion/CompanionTextures.ts index 84eaf38f..279b027d 100644 --- a/front/src/Phaser/Companion/CompanionTextures.ts +++ b/front/src/Phaser/Companion/CompanionTextures.ts @@ -1,7 +1,7 @@ export interface CompanionResourceDescriptionInterface { - name: string, - img: string, - behaviour: "dog" | "cat" + name: string; + img: string; + behaviour: "dog" | "cat"; } export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [ @@ -11,4 +11,4 @@ export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [ { name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" }, { name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" }, { name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" }, -] +]; diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index 75c20a48..bd87ba75 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -7,23 +7,23 @@ export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourc }); return COMPANION_RESOURCES; -} +}; export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise => { return new Promise((resolve, reject) => { - const resource = COMPANION_RESOURCES.find(item => item.name === name); + const resource = COMPANION_RESOURCES.find((item) => item.name === name); - if (typeof resource === 'undefined') { + if (typeof resource === "undefined") { return reject(`Texture '${name}' not found!`); } - + if (loader.textureManager.exists(resource.name)) { return resolve(resource.name); } - + loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 }); loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name)); - + loader.start(); // It's only automatically started during the Scene preload. }); -} +}; diff --git a/front/src/Phaser/Components/ChatModeIcon.ts b/front/src/Phaser/Components/ChatModeIcon.ts index 69449a1d..cba5a447 100644 --- a/front/src/Phaser/Components/ChatModeIcon.ts +++ b/front/src/Phaser/Components/ChatModeIcon.ts @@ -2,7 +2,7 @@ import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; export class ChatModeIcon extends Phaser.GameObjects.Sprite { constructor(scene: Phaser.Scene, x: number, y: number) { - super(scene, x, y, 'layout_modes', 3); + super(scene, x, y, "layout_modes", 3); scene.add.existing(this); this.setScrollFactor(0, 0); this.setOrigin(0, 1); @@ -10,4 +10,4 @@ export class ChatModeIcon extends Phaser.GameObjects.Sprite { this.setVisible(false); this.setDepth(DEPTH_INGAME_TEXT_INDEX); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Components/ClickButton.ts b/front/src/Phaser/Components/ClickButton.ts index 204e9542..65aaadbe 100644 --- a/front/src/Phaser/Components/ClickButton.ts +++ b/front/src/Phaser/Components/ClickButton.ts @@ -1,11 +1,8 @@ - -export class ClickButton extends Phaser.GameObjects.Image{ - +export class ClickButton extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number, textureName: string, callback: Function) { super(scene, x, y, textureName); this.scene.add.existing(this); this.setInteractive(); this.on("pointerup", callback); } - -} \ No newline at end of file +} diff --git a/front/src/Phaser/Components/PresentationModeIcon.ts b/front/src/Phaser/Components/PresentationModeIcon.ts index 09c8beb5..5f7c8d2f 100644 --- a/front/src/Phaser/Components/PresentationModeIcon.ts +++ b/front/src/Phaser/Components/PresentationModeIcon.ts @@ -1,8 +1,8 @@ -import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; +import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; export class PresentationModeIcon extends Phaser.GameObjects.Sprite { constructor(scene: Phaser.Scene, x: number, y: number) { - super(scene, x, y, 'layout_modes', 0); + super(scene, x, y, "layout_modes", 0); scene.add.existing(this); this.setScrollFactor(0, 0); this.setOrigin(0, 1); @@ -10,4 +10,4 @@ export class PresentationModeIcon extends Phaser.GameObjects.Sprite { this.setVisible(false); this.setDepth(DEPTH_INGAME_TEXT_INDEX); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Components/RadialMenu.ts b/front/src/Phaser/Components/RadialMenu.ts index 1094f73a..c82bc4bf 100644 --- a/front/src/Phaser/Components/RadialMenu.ts +++ b/front/src/Phaser/Components/RadialMenu.ts @@ -1,20 +1,20 @@ import Sprite = Phaser.GameObjects.Sprite; -import {DEPTH_UI_INDEX} from "../Game/DepthIndexes"; -import {waScaleManager} from "../Services/WaScaleManager"; +import { DEPTH_UI_INDEX } from "../Game/DepthIndexes"; +import { waScaleManager } from "../Services/WaScaleManager"; export interface RadialMenuItem { - image: string, - name: string, + image: string; + name: string; } -export const RadialMenuClickEvent = 'radialClick'; +export const RadialMenuClickEvent = "radialClick"; export class RadialMenu extends Phaser.GameObjects.Container { private resizeCallback: OmitThisParameter<() => void>; - + constructor(scene: Phaser.Scene, x: number, y: number, private items: RadialMenuItem[]) { super(scene, x, y); - this.setDepth(DEPTH_UI_INDEX) + this.setDepth(DEPTH_UI_INDEX); this.scene.add.existing(this); this.initItems(); @@ -22,45 +22,45 @@ export class RadialMenu extends Phaser.GameObjects.Container { this.resizeCallback = this.resize.bind(this); this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback); } - + private initItems() { const itemsNumber = this.items.length; const menuRadius = 70 + (waScaleManager.uiScalingFactor - 1) * 20; - this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius)) + this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius)); } - + private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number, menuRadius: number) { - const image = new Sprite(this.scene, 0, menuRadius, item.image); + const image = new Sprite(this.scene, 0, menuRadius, item.image); this.add(image); this.scene.sys.updateList.add(image); const scalingFactor = waScaleManager.uiScalingFactor * 0.075; - image.setScale(scalingFactor) + image.setScale(scalingFactor); image.setInteractive({ useHandCursor: true, }); - image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item)); - image.on('pointerover', () => { + image.on("pointerdown", () => this.emit(RadialMenuClickEvent, item)); + image.on("pointerover", () => { this.scene.tweens.add({ targets: image, props: { scale: 2 * scalingFactor, }, duration: 500, - ease: 'Power3', - }) + ease: "Power3", + }); }); - image.on('pointerout', () => { + image.on("pointerout", () => { this.scene.tweens.add({ targets: image, props: { scale: scalingFactor, }, duration: 500, - ease: 'Power3', - }) + ease: "Power3", + }); }); - const angle = 2 * Math.PI * index / itemsNumber; - Phaser.Actions.RotateAroundDistance([image], {x: 0, y: 0}, angle, menuRadius); + const angle = (2 * Math.PI * index) / itemsNumber; + Phaser.Actions.RotateAroundDistance([image], { x: 0, y: 0 }, angle, menuRadius); } private resize() { @@ -71,4 +71,4 @@ export class RadialMenu extends Phaser.GameObjects.Container { this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback); super.destroy(); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts index 53802d31..4e7f4e8b 100644 --- a/front/src/Phaser/Components/SoundMeter.ts +++ b/front/src/Phaser/Components/SoundMeter.ts @@ -1,4 +1,4 @@ -import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context'; +import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context"; /** * Class to measure the sound volume of a media stream @@ -7,10 +7,10 @@ export class SoundMeter { private instant: number; private clip: number; //private script: ScriptProcessorNode; - private analyser: IAnalyserNode|undefined; - private dataArray: Uint8Array|undefined; - private context: IAudioContext|undefined; - private source: IMediaStreamAudioSourceNode|undefined; + private analyser: IAnalyserNode | undefined; + private dataArray: Uint8Array | undefined; + private context: IAudioContext | undefined; + private source: IMediaStreamAudioSourceNode | undefined; constructor() { this.instant = 0.0; @@ -27,8 +27,7 @@ export class SoundMeter { this.dataArray = new Uint8Array(bufferLength); } - public connectToSource(stream: MediaStream, context: IAudioContext): void - { + public connectToSource(stream: MediaStream, context: IAudioContext): void { if (this.source !== undefined) { this.stop(); } @@ -42,8 +41,6 @@ export class SoundMeter { //analyser.connect(distortion); //distortion.connect(this.context.destination); //this.analyser.connect(this.context.destination); - - } public getVolume(): number { @@ -52,16 +49,15 @@ export class SoundMeter { } this.analyser.getByteFrequencyData(this.dataArray); - const input = this.dataArray; let i; let sum = 0.0; //let clipcount = 0; for (i = 0; i < input.length; ++i) { sum += input[i] * input[i]; - // if (Math.abs(input[i]) > 0.99) { - // clipcount += 1; - // } + // if (Math.abs(input[i]) > 0.99) { + // clipcount += 1; + // } } this.instant = Math.sqrt(sum / input.length); //this.slow = 0.95 * that.slow + 0.05 * that.instant; @@ -84,6 +80,4 @@ export class SoundMeter { this.dataArray = undefined; this.source = undefined; } - } - diff --git a/front/src/Phaser/Components/TextField.ts b/front/src/Phaser/Components/TextField.ts index 4eb6f41b..29f32497 100644 --- a/front/src/Phaser/Components/TextField.ts +++ b/front/src/Phaser/Components/TextField.ts @@ -1,10 +1,9 @@ - export class TextField extends Phaser.GameObjects.BitmapText { constructor(scene: Phaser.Scene, x: number, y: number, text: string | string[], center: boolean = true) { - super(scene, x, y, 'main_font', text, 8); + super(scene, x, y, "main_font", text, 8); this.scene.add.existing(this); if (center) { - this.setOrigin(0.5).setCenterAlign() + this.setOrigin(0.5).setCenterAlign(); } } } diff --git a/front/src/Phaser/Entity/CustomizedCharacter.ts b/front/src/Phaser/Entity/CustomizedCharacter.ts index 3a7f1597..79ac8ebc 100644 --- a/front/src/Phaser/Entity/CustomizedCharacter.ts +++ b/front/src/Phaser/Entity/CustomizedCharacter.ts @@ -1,5 +1,5 @@ import Container = Phaser.GameObjects.Container; -import type {Scene} from "phaser"; +import type { Scene } from "phaser"; import Sprite = Phaser.GameObjects.Sprite; /** diff --git a/front/src/Phaser/Entity/PlayerTextures.ts b/front/src/Phaser/Entity/PlayerTextures.ts index d0542d6a..d4376c66 100644 --- a/front/src/Phaser/Entity/PlayerTextures.ts +++ b/front/src/Phaser/Entity/PlayerTextures.ts @@ -1,333 +1,439 @@ //The list of all the player textures, both the default models and the partial textures used for customization export interface BodyResourceDescriptionListInterface { - [key: string]: BodyResourceDescriptionInterface + [key: string]: BodyResourceDescriptionInterface; } export interface BodyResourceDescriptionInterface { - name: string, - img: string, - level?: number + name: string; + img: string; + level?: number; } export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = { - "male1": {name: "male1", img: "resources/characters/pipoya/Male 01-1.png"}, - "male2": {name: "male2", img: "resources/characters/pipoya/Male 02-2.png"}, - "male3": {name: "male3", img: "resources/characters/pipoya/Male 03-4.png"}, - "male4": {name: "male4", img: "resources/characters/pipoya/Male 09-1.png"}, - "male5": {name: "male5", img: "resources/characters/pipoya/Male 10-3.png"}, - "male6": {name: "male6", img: "resources/characters/pipoya/Male 17-2.png"}, - "male7": {name: "male7", img: "resources/characters/pipoya/Male 18-1.png"}, - "male8": {name: "male8", img: "resources/characters/pipoya/Male 16-4.png"}, - "male9": {name: "male9", img: "resources/characters/pipoya/Male 07-2.png"}, - "male10": {name: "male10", img: "resources/characters/pipoya/Male 05-3.png"}, - "male11": {name: "male11", img: "resources/characters/pipoya/Teacher male 02.png"}, - "male12": {name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png"}, + male1: { name: "male1", img: "resources/characters/pipoya/Male 01-1.png" }, + male2: { name: "male2", img: "resources/characters/pipoya/Male 02-2.png" }, + male3: { name: "male3", img: "resources/characters/pipoya/Male 03-4.png" }, + male4: { name: "male4", img: "resources/characters/pipoya/Male 09-1.png" }, + male5: { name: "male5", img: "resources/characters/pipoya/Male 10-3.png" }, + male6: { name: "male6", img: "resources/characters/pipoya/Male 17-2.png" }, + male7: { name: "male7", img: "resources/characters/pipoya/Male 18-1.png" }, + male8: { name: "male8", img: "resources/characters/pipoya/Male 16-4.png" }, + male9: { name: "male9", img: "resources/characters/pipoya/Male 07-2.png" }, + male10: { name: "male10", img: "resources/characters/pipoya/Male 05-3.png" }, + male11: { name: "male11", img: "resources/characters/pipoya/Teacher male 02.png" }, + male12: { name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png" }, - "Female1": {name: "Female1", img: "resources/characters/pipoya/Female 01-1.png"}, - "Female2": {name: "Female2", img: "resources/characters/pipoya/Female 02-2.png"}, - "Female3": {name: "Female3", img: "resources/characters/pipoya/Female 03-4.png"}, - "Female4": {name: "Female4", img: "resources/characters/pipoya/Female 09-1.png"}, - "Female5": {name: "Female5", img: "resources/characters/pipoya/Female 10-3.png"}, - "Female6": {name: "Female6", img: "resources/characters/pipoya/Female 17-2.png"}, - "Female7": {name: "Female7", img: "resources/characters/pipoya/Female 18-1.png"}, - "Female8": {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"}, - "Female9": {name: "Female9", img: "resources/characters/pipoya/Female 07-2.png"}, - "Female10": {name: "Female10", img: "resources/characters/pipoya/Female 05-3.png"}, - "Female11": {name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png"}, - "Female12": {name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png"}, + Female1: { name: "Female1", img: "resources/characters/pipoya/Female 01-1.png" }, + Female2: { name: "Female2", img: "resources/characters/pipoya/Female 02-2.png" }, + Female3: { name: "Female3", img: "resources/characters/pipoya/Female 03-4.png" }, + Female4: { name: "Female4", img: "resources/characters/pipoya/Female 09-1.png" }, + Female5: { name: "Female5", img: "resources/characters/pipoya/Female 10-3.png" }, + Female6: { name: "Female6", img: "resources/characters/pipoya/Female 17-2.png" }, + Female7: { name: "Female7", img: "resources/characters/pipoya/Female 18-1.png" }, + Female8: { name: "Female8", img: "resources/characters/pipoya/Female 16-4.png" }, + Female9: { name: "Female9", img: "resources/characters/pipoya/Female 07-2.png" }, + Female10: { name: "Female10", img: "resources/characters/pipoya/Female 05-3.png" }, + Female11: { name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png" }, + Female12: { name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png" }, }; export const COLOR_RESOURCES: BodyResourceDescriptionListInterface = { - "color_1": {name: "color_1", img: "resources/customisation/character_color/character_color0.png"}, - "color_2": {name: "color_2", img: "resources/customisation/character_color/character_color1.png"}, - "color_3": {name: "color_3", img: "resources/customisation/character_color/character_color2.png"}, - "color_4": {name: "color_4", img: "resources/customisation/character_color/character_color3.png"}, - "color_5": {name: "color_5", img: "resources/customisation/character_color/character_color4.png"}, - "color_6": {name: "color_6", img: "resources/customisation/character_color/character_color5.png"}, - "color_7": {name: "color_7", img: "resources/customisation/character_color/character_color6.png"}, - "color_8": {name: "color_8", img: "resources/customisation/character_color/character_color7.png"}, - "color_9": {name: "color_9", img: "resources/customisation/character_color/character_color8.png"}, - "color_10": {name: "color_10", img: "resources/customisation/character_color/character_color9.png"}, - "color_11": {name: "color_11", img: "resources/customisation/character_color/character_color10.png"}, - "color_12": {name: "color_12", img: "resources/customisation/character_color/character_color11.png"}, - "color_13": {name: "color_13", img: "resources/customisation/character_color/character_color12.png"}, - "color_14": {name: "color_14", img: "resources/customisation/character_color/character_color13.png"}, - "color_15": {name: "color_15", img: "resources/customisation/character_color/character_color14.png"}, - "color_16": {name: "color_16", img: "resources/customisation/character_color/character_color15.png"}, - "color_17": {name: "color_17", img: "resources/customisation/character_color/character_color16.png"}, - "color_18": {name: "color_18", img: "resources/customisation/character_color/character_color17.png"}, - "color_19": {name: "color_19", img: "resources/customisation/character_color/character_color18.png"}, - "color_20": {name: "color_20", img: "resources/customisation/character_color/character_color19.png"}, - "color_21": {name: "color_21", img: "resources/customisation/character_color/character_color20.png"}, - "color_22": {name: "color_22", img: "resources/customisation/character_color/character_color21.png"}, - "color_23": {name: "color_23", img: "resources/customisation/character_color/character_color22.png"}, - "color_24": {name: "color_24", img: "resources/customisation/character_color/character_color23.png"}, - "color_25": {name: "color_25", img: "resources/customisation/character_color/character_color24.png"}, - "color_26": {name: "color_26", img: "resources/customisation/character_color/character_color25.png"}, - "color_27": {name: "color_27", img: "resources/customisation/character_color/character_color26.png"}, - "color_28": {name: "color_28", img: "resources/customisation/character_color/character_color27.png"}, - "color_29": {name: "color_29", img: "resources/customisation/character_color/character_color28.png"}, - "color_30": {name: "color_30", img: "resources/customisation/character_color/character_color29.png"}, - "color_31": {name: "color_31", img: "resources/customisation/character_color/character_color30.png"}, - "color_32": {name: "color_32", img: "resources/customisation/character_color/character_color31.png"}, - "color_33": {name: "color_33", img: "resources/customisation/character_color/character_color32.png"} + color_1: { name: "color_1", img: "resources/customisation/character_color/character_color0.png" }, + color_2: { name: "color_2", img: "resources/customisation/character_color/character_color1.png" }, + color_3: { name: "color_3", img: "resources/customisation/character_color/character_color2.png" }, + color_4: { name: "color_4", img: "resources/customisation/character_color/character_color3.png" }, + color_5: { name: "color_5", img: "resources/customisation/character_color/character_color4.png" }, + color_6: { name: "color_6", img: "resources/customisation/character_color/character_color5.png" }, + color_7: { name: "color_7", img: "resources/customisation/character_color/character_color6.png" }, + color_8: { name: "color_8", img: "resources/customisation/character_color/character_color7.png" }, + color_9: { name: "color_9", img: "resources/customisation/character_color/character_color8.png" }, + color_10: { name: "color_10", img: "resources/customisation/character_color/character_color9.png" }, + color_11: { name: "color_11", img: "resources/customisation/character_color/character_color10.png" }, + color_12: { name: "color_12", img: "resources/customisation/character_color/character_color11.png" }, + color_13: { name: "color_13", img: "resources/customisation/character_color/character_color12.png" }, + color_14: { name: "color_14", img: "resources/customisation/character_color/character_color13.png" }, + color_15: { name: "color_15", img: "resources/customisation/character_color/character_color14.png" }, + color_16: { name: "color_16", img: "resources/customisation/character_color/character_color15.png" }, + color_17: { name: "color_17", img: "resources/customisation/character_color/character_color16.png" }, + color_18: { name: "color_18", img: "resources/customisation/character_color/character_color17.png" }, + color_19: { name: "color_19", img: "resources/customisation/character_color/character_color18.png" }, + color_20: { name: "color_20", img: "resources/customisation/character_color/character_color19.png" }, + color_21: { name: "color_21", img: "resources/customisation/character_color/character_color20.png" }, + color_22: { name: "color_22", img: "resources/customisation/character_color/character_color21.png" }, + color_23: { name: "color_23", img: "resources/customisation/character_color/character_color22.png" }, + color_24: { name: "color_24", img: "resources/customisation/character_color/character_color23.png" }, + color_25: { name: "color_25", img: "resources/customisation/character_color/character_color24.png" }, + color_26: { name: "color_26", img: "resources/customisation/character_color/character_color25.png" }, + color_27: { name: "color_27", img: "resources/customisation/character_color/character_color26.png" }, + color_28: { name: "color_28", img: "resources/customisation/character_color/character_color27.png" }, + color_29: { name: "color_29", img: "resources/customisation/character_color/character_color28.png" }, + color_30: { name: "color_30", img: "resources/customisation/character_color/character_color29.png" }, + color_31: { name: "color_31", img: "resources/customisation/character_color/character_color30.png" }, + color_32: { name: "color_32", img: "resources/customisation/character_color/character_color31.png" }, + color_33: { name: "color_33", img: "resources/customisation/character_color/character_color32.png" }, }; export const EYES_RESOURCES: BodyResourceDescriptionListInterface = { - "eyes_1": {name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png"}, - "eyes_2": {name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png"}, - "eyes_3": {name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png"}, - "eyes_4": {name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png"}, - "eyes_5": {name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png"}, - "eyes_6": {name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png"}, - "eyes_7": {name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png"}, - "eyes_8": {name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png"}, - "eyes_9": {name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png"}, - "eyes_10": {name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png"}, - "eyes_11": {name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png"}, - "eyes_12": {name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png"}, - "eyes_13": {name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png"}, - "eyes_14": {name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png"}, - "eyes_15": {name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png"}, - "eyes_16": {name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png"}, - "eyes_17": {name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png"}, - "eyes_18": {name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png"}, - "eyes_19": {name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png"}, - "eyes_20": {name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png"}, - "eyes_21": {name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png"}, - "eyes_22": {name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png"}, - "eyes_23": {name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png"}, - "eyes_24": {name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png"}, - "eyes_25": {name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png"}, - "eyes_26": {name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png"}, - "eyes_27": {name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png"}, - "eyes_28": {name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png"}, - "eyes_29": {name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png"}, - "eyes_30": {name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png"} - + eyes_1: { name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png" }, + eyes_2: { name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png" }, + eyes_3: { name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png" }, + eyes_4: { name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png" }, + eyes_5: { name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png" }, + eyes_6: { name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png" }, + eyes_7: { name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png" }, + eyes_8: { name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png" }, + eyes_9: { name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png" }, + eyes_10: { name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png" }, + eyes_11: { name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png" }, + eyes_12: { name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png" }, + eyes_13: { name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png" }, + eyes_14: { name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png" }, + eyes_15: { name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png" }, + eyes_16: { name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png" }, + eyes_17: { name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png" }, + eyes_18: { name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png" }, + eyes_19: { name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png" }, + eyes_20: { name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png" }, + eyes_21: { name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png" }, + eyes_22: { name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png" }, + eyes_23: { name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png" }, + eyes_24: { name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png" }, + eyes_25: { name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png" }, + eyes_26: { name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png" }, + eyes_27: { name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png" }, + eyes_28: { name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png" }, + eyes_29: { name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png" }, + eyes_30: { name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png" }, }; export const HAIR_RESOURCES: BodyResourceDescriptionListInterface = { - "hair_1": {name:"hair_1", img: "resources/customisation/character_hairs/character_hairs0.png"}, - "hair_2": {name:"hair_2", img: "resources/customisation/character_hairs/character_hairs1.png"}, - "hair_3": {name:"hair_3", img: "resources/customisation/character_hairs/character_hairs2.png"}, - "hair_4": {name:"hair_4", img: "resources/customisation/character_hairs/character_hairs3.png"}, - "hair_5": {name:"hair_5", img: "resources/customisation/character_hairs/character_hairs4.png"}, - "hair_6": {name:"hair_6", img: "resources/customisation/character_hairs/character_hairs5.png"}, - "hair_7": {name:"hair_7", img: "resources/customisation/character_hairs/character_hairs6.png"}, - "hair_8": {name:"hair_8", img: "resources/customisation/character_hairs/character_hairs7.png"}, - "hair_9": {name:"hair_9", img: "resources/customisation/character_hairs/character_hairs8.png"}, - "hair_10": {name:"hair_10",img: "resources/customisation/character_hairs/character_hairs9.png"}, - "hair_11": {name:"hair_11",img: "resources/customisation/character_hairs/character_hairs10.png"}, - "hair_12": {name:"hair_12",img: "resources/customisation/character_hairs/character_hairs11.png"}, - "hair_13": {name:"hair_13",img: "resources/customisation/character_hairs/character_hairs12.png"}, - "hair_14": {name:"hair_14",img: "resources/customisation/character_hairs/character_hairs13.png"}, - "hair_15": {name:"hair_15",img: "resources/customisation/character_hairs/character_hairs14.png"}, - "hair_16": {name:"hair_16",img: "resources/customisation/character_hairs/character_hairs15.png"}, - "hair_17": {name:"hair_17",img: "resources/customisation/character_hairs/character_hairs16.png"}, - "hair_18": {name:"hair_18",img: "resources/customisation/character_hairs/character_hairs17.png"}, - "hair_19": {name:"hair_19",img: "resources/customisation/character_hairs/character_hairs18.png"}, - "hair_20": {name:"hair_20",img: "resources/customisation/character_hairs/character_hairs19.png"}, - "hair_21": {name:"hair_21",img: "resources/customisation/character_hairs/character_hairs20.png"}, - "hair_22": {name:"hair_22",img: "resources/customisation/character_hairs/character_hairs21.png"}, - "hair_23": {name:"hair_23",img: "resources/customisation/character_hairs/character_hairs22.png"}, - "hair_24": {name:"hair_24",img: "resources/customisation/character_hairs/character_hairs23.png"}, - "hair_25": {name:"hair_25",img: "resources/customisation/character_hairs/character_hairs24.png"}, - "hair_26": {name:"hair_26",img: "resources/customisation/character_hairs/character_hairs25.png"}, - "hair_27": {name:"hair_27",img: "resources/customisation/character_hairs/character_hairs26.png"}, - "hair_28": {name:"hair_28",img: "resources/customisation/character_hairs/character_hairs27.png"}, - "hair_29": {name:"hair_29",img: "resources/customisation/character_hairs/character_hairs28.png"}, - "hair_30": {name:"hair_30",img: "resources/customisation/character_hairs/character_hairs29.png"}, - "hair_31": {name:"hair_31",img: "resources/customisation/character_hairs/character_hairs30.png"}, - "hair_32": {name:"hair_32",img: "resources/customisation/character_hairs/character_hairs31.png"}, - "hair_33": {name:"hair_33",img: "resources/customisation/character_hairs/character_hairs32.png"}, - "hair_34": {name:"hair_34",img: "resources/customisation/character_hairs/character_hairs33.png"}, - "hair_35": {name:"hair_35",img: "resources/customisation/character_hairs/character_hairs34.png"}, - "hair_36": {name:"hair_36",img: "resources/customisation/character_hairs/character_hairs35.png"}, - "hair_37": {name:"hair_37",img: "resources/customisation/character_hairs/character_hairs36.png"}, - "hair_38": {name:"hair_38",img: "resources/customisation/character_hairs/character_hairs37.png"}, - "hair_39": {name:"hair_39",img: "resources/customisation/character_hairs/character_hairs38.png"}, - "hair_40": {name:"hair_40",img: "resources/customisation/character_hairs/character_hairs39.png"}, - "hair_41": {name:"hair_41",img: "resources/customisation/character_hairs/character_hairs40.png"}, - "hair_42": {name:"hair_42",img: "resources/customisation/character_hairs/character_hairs41.png"}, - "hair_43": {name:"hair_43",img: "resources/customisation/character_hairs/character_hairs42.png"}, - "hair_44": {name:"hair_44",img: "resources/customisation/character_hairs/character_hairs43.png"}, - "hair_45": {name:"hair_45",img: "resources/customisation/character_hairs/character_hairs44.png"}, - "hair_46": {name:"hair_46",img: "resources/customisation/character_hairs/character_hairs45.png"}, - "hair_47": {name:"hair_47",img: "resources/customisation/character_hairs/character_hairs46.png"}, - "hair_48": {name:"hair_48",img: "resources/customisation/character_hairs/character_hairs47.png"}, - "hair_49": {name:"hair_49",img: "resources/customisation/character_hairs/character_hairs48.png"}, - "hair_50": {name:"hair_50",img: "resources/customisation/character_hairs/character_hairs49.png"}, - "hair_51": {name:"hair_51",img: "resources/customisation/character_hairs/character_hairs50.png"}, - "hair_52": {name:"hair_52",img: "resources/customisation/character_hairs/character_hairs51.png"}, - "hair_53": {name:"hair_53",img: "resources/customisation/character_hairs/character_hairs52.png"}, - "hair_54": {name:"hair_54",img: "resources/customisation/character_hairs/character_hairs53.png"}, - "hair_55": {name:"hair_55",img: "resources/customisation/character_hairs/character_hairs54.png"}, - "hair_56": {name:"hair_56",img: "resources/customisation/character_hairs/character_hairs55.png"}, - "hair_57": {name:"hair_57",img: "resources/customisation/character_hairs/character_hairs56.png"}, - "hair_58": {name:"hair_58",img: "resources/customisation/character_hairs/character_hairs57.png"}, - "hair_59": {name:"hair_59",img: "resources/customisation/character_hairs/character_hairs58.png"}, - "hair_60": {name:"hair_60",img: "resources/customisation/character_hairs/character_hairs59.png"}, - "hair_61": {name:"hair_61",img: "resources/customisation/character_hairs/character_hairs60.png"}, - "hair_62": {name:"hair_62",img: "resources/customisation/character_hairs/character_hairs61.png"}, - "hair_63": {name:"hair_63",img: "resources/customisation/character_hairs/character_hairs62.png"}, - "hair_64": {name:"hair_64",img: "resources/customisation/character_hairs/character_hairs63.png"}, - "hair_65": {name:"hair_65",img: "resources/customisation/character_hairs/character_hairs64.png"}, - "hair_66": {name:"hair_66",img: "resources/customisation/character_hairs/character_hairs65.png"}, - "hair_67": {name:"hair_67",img: "resources/customisation/character_hairs/character_hairs66.png"}, - "hair_68": {name:"hair_68",img: "resources/customisation/character_hairs/character_hairs67.png"}, - "hair_69": {name:"hair_69",img: "resources/customisation/character_hairs/character_hairs68.png"}, - "hair_70": {name:"hair_70",img: "resources/customisation/character_hairs/character_hairs69.png"}, - "hair_71": {name:"hair_71",img: "resources/customisation/character_hairs/character_hairs70.png"}, - "hair_72": {name:"hair_72",img: "resources/customisation/character_hairs/character_hairs71.png"}, - "hair_73": {name:"hair_73",img: "resources/customisation/character_hairs/character_hairs72.png"}, - "hair_74": {name:"hair_74",img: "resources/customisation/character_hairs/character_hairs73.png"} + hair_1: { name: "hair_1", img: "resources/customisation/character_hairs/character_hairs0.png" }, + hair_2: { name: "hair_2", img: "resources/customisation/character_hairs/character_hairs1.png" }, + hair_3: { name: "hair_3", img: "resources/customisation/character_hairs/character_hairs2.png" }, + hair_4: { name: "hair_4", img: "resources/customisation/character_hairs/character_hairs3.png" }, + hair_5: { name: "hair_5", img: "resources/customisation/character_hairs/character_hairs4.png" }, + hair_6: { name: "hair_6", img: "resources/customisation/character_hairs/character_hairs5.png" }, + hair_7: { name: "hair_7", img: "resources/customisation/character_hairs/character_hairs6.png" }, + hair_8: { name: "hair_8", img: "resources/customisation/character_hairs/character_hairs7.png" }, + hair_9: { name: "hair_9", img: "resources/customisation/character_hairs/character_hairs8.png" }, + hair_10: { name: "hair_10", img: "resources/customisation/character_hairs/character_hairs9.png" }, + hair_11: { name: "hair_11", img: "resources/customisation/character_hairs/character_hairs10.png" }, + hair_12: { name: "hair_12", img: "resources/customisation/character_hairs/character_hairs11.png" }, + hair_13: { name: "hair_13", img: "resources/customisation/character_hairs/character_hairs12.png" }, + hair_14: { name: "hair_14", img: "resources/customisation/character_hairs/character_hairs13.png" }, + hair_15: { name: "hair_15", img: "resources/customisation/character_hairs/character_hairs14.png" }, + hair_16: { name: "hair_16", img: "resources/customisation/character_hairs/character_hairs15.png" }, + hair_17: { name: "hair_17", img: "resources/customisation/character_hairs/character_hairs16.png" }, + hair_18: { name: "hair_18", img: "resources/customisation/character_hairs/character_hairs17.png" }, + hair_19: { name: "hair_19", img: "resources/customisation/character_hairs/character_hairs18.png" }, + hair_20: { name: "hair_20", img: "resources/customisation/character_hairs/character_hairs19.png" }, + hair_21: { name: "hair_21", img: "resources/customisation/character_hairs/character_hairs20.png" }, + hair_22: { name: "hair_22", img: "resources/customisation/character_hairs/character_hairs21.png" }, + hair_23: { name: "hair_23", img: "resources/customisation/character_hairs/character_hairs22.png" }, + hair_24: { name: "hair_24", img: "resources/customisation/character_hairs/character_hairs23.png" }, + hair_25: { name: "hair_25", img: "resources/customisation/character_hairs/character_hairs24.png" }, + hair_26: { name: "hair_26", img: "resources/customisation/character_hairs/character_hairs25.png" }, + hair_27: { name: "hair_27", img: "resources/customisation/character_hairs/character_hairs26.png" }, + hair_28: { name: "hair_28", img: "resources/customisation/character_hairs/character_hairs27.png" }, + hair_29: { name: "hair_29", img: "resources/customisation/character_hairs/character_hairs28.png" }, + hair_30: { name: "hair_30", img: "resources/customisation/character_hairs/character_hairs29.png" }, + hair_31: { name: "hair_31", img: "resources/customisation/character_hairs/character_hairs30.png" }, + hair_32: { name: "hair_32", img: "resources/customisation/character_hairs/character_hairs31.png" }, + hair_33: { name: "hair_33", img: "resources/customisation/character_hairs/character_hairs32.png" }, + hair_34: { name: "hair_34", img: "resources/customisation/character_hairs/character_hairs33.png" }, + hair_35: { name: "hair_35", img: "resources/customisation/character_hairs/character_hairs34.png" }, + hair_36: { name: "hair_36", img: "resources/customisation/character_hairs/character_hairs35.png" }, + hair_37: { name: "hair_37", img: "resources/customisation/character_hairs/character_hairs36.png" }, + hair_38: { name: "hair_38", img: "resources/customisation/character_hairs/character_hairs37.png" }, + hair_39: { name: "hair_39", img: "resources/customisation/character_hairs/character_hairs38.png" }, + hair_40: { name: "hair_40", img: "resources/customisation/character_hairs/character_hairs39.png" }, + hair_41: { name: "hair_41", img: "resources/customisation/character_hairs/character_hairs40.png" }, + hair_42: { name: "hair_42", img: "resources/customisation/character_hairs/character_hairs41.png" }, + hair_43: { name: "hair_43", img: "resources/customisation/character_hairs/character_hairs42.png" }, + hair_44: { name: "hair_44", img: "resources/customisation/character_hairs/character_hairs43.png" }, + hair_45: { name: "hair_45", img: "resources/customisation/character_hairs/character_hairs44.png" }, + hair_46: { name: "hair_46", img: "resources/customisation/character_hairs/character_hairs45.png" }, + hair_47: { name: "hair_47", img: "resources/customisation/character_hairs/character_hairs46.png" }, + hair_48: { name: "hair_48", img: "resources/customisation/character_hairs/character_hairs47.png" }, + hair_49: { name: "hair_49", img: "resources/customisation/character_hairs/character_hairs48.png" }, + hair_50: { name: "hair_50", img: "resources/customisation/character_hairs/character_hairs49.png" }, + hair_51: { name: "hair_51", img: "resources/customisation/character_hairs/character_hairs50.png" }, + hair_52: { name: "hair_52", img: "resources/customisation/character_hairs/character_hairs51.png" }, + hair_53: { name: "hair_53", img: "resources/customisation/character_hairs/character_hairs52.png" }, + hair_54: { name: "hair_54", img: "resources/customisation/character_hairs/character_hairs53.png" }, + hair_55: { name: "hair_55", img: "resources/customisation/character_hairs/character_hairs54.png" }, + hair_56: { name: "hair_56", img: "resources/customisation/character_hairs/character_hairs55.png" }, + hair_57: { name: "hair_57", img: "resources/customisation/character_hairs/character_hairs56.png" }, + hair_58: { name: "hair_58", img: "resources/customisation/character_hairs/character_hairs57.png" }, + hair_59: { name: "hair_59", img: "resources/customisation/character_hairs/character_hairs58.png" }, + hair_60: { name: "hair_60", img: "resources/customisation/character_hairs/character_hairs59.png" }, + hair_61: { name: "hair_61", img: "resources/customisation/character_hairs/character_hairs60.png" }, + hair_62: { name: "hair_62", img: "resources/customisation/character_hairs/character_hairs61.png" }, + hair_63: { name: "hair_63", img: "resources/customisation/character_hairs/character_hairs62.png" }, + hair_64: { name: "hair_64", img: "resources/customisation/character_hairs/character_hairs63.png" }, + hair_65: { name: "hair_65", img: "resources/customisation/character_hairs/character_hairs64.png" }, + hair_66: { name: "hair_66", img: "resources/customisation/character_hairs/character_hairs65.png" }, + hair_67: { name: "hair_67", img: "resources/customisation/character_hairs/character_hairs66.png" }, + hair_68: { name: "hair_68", img: "resources/customisation/character_hairs/character_hairs67.png" }, + hair_69: { name: "hair_69", img: "resources/customisation/character_hairs/character_hairs68.png" }, + hair_70: { name: "hair_70", img: "resources/customisation/character_hairs/character_hairs69.png" }, + hair_71: { name: "hair_71", img: "resources/customisation/character_hairs/character_hairs70.png" }, + hair_72: { name: "hair_72", img: "resources/customisation/character_hairs/character_hairs71.png" }, + hair_73: { name: "hair_73", img: "resources/customisation/character_hairs/character_hairs72.png" }, + hair_74: { name: "hair_74", img: "resources/customisation/character_hairs/character_hairs73.png" }, }; - export const CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = { - "clothes_1": {name:"clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png"}, - "clothes_2": {name:"clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png"}, - "clothes_3": {name:"clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png"}, - "clothes_4": {name:"clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png"}, - "clothes_5": {name:"clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png"}, - "clothes_6": {name:"clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png"}, - "clothes_7": {name:"clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png"}, - "clothes_8": {name:"clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png"}, - "clothes_9": {name:"clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png"}, - "clothes_10": {name:"clothes_10",img: "resources/customisation/character_clothes/character_clothes9.png"}, - "clothes_11": {name:"clothes_11",img: "resources/customisation/character_clothes/character_clothes10.png"}, - "clothes_12": {name:"clothes_12",img: "resources/customisation/character_clothes/character_clothes11.png"}, - "clothes_13": {name:"clothes_13",img: "resources/customisation/character_clothes/character_clothes12.png"}, - "clothes_14": {name:"clothes_14",img: "resources/customisation/character_clothes/character_clothes13.png"}, - "clothes_15": {name:"clothes_15",img: "resources/customisation/character_clothes/character_clothes14.png"}, - "clothes_16": {name:"clothes_16",img: "resources/customisation/character_clothes/character_clothes15.png"}, - "clothes_17": {name:"clothes_17",img: "resources/customisation/character_clothes/character_clothes16.png"}, - "clothes_18": {name:"clothes_18",img: "resources/customisation/character_clothes/character_clothes17.png"}, - "clothes_19": {name:"clothes_19",img: "resources/customisation/character_clothes/character_clothes18.png"}, - "clothes_20": {name:"clothes_20",img: "resources/customisation/character_clothes/character_clothes19.png"}, - "clothes_21": {name:"clothes_21",img: "resources/customisation/character_clothes/character_clothes20.png"}, - "clothes_22": {name:"clothes_22",img: "resources/customisation/character_clothes/character_clothes21.png"}, - "clothes_23": {name:"clothes_23",img: "resources/customisation/character_clothes/character_clothes22.png"}, - "clothes_24": {name:"clothes_24",img: "resources/customisation/character_clothes/character_clothes23.png"}, - "clothes_25": {name:"clothes_25",img: "resources/customisation/character_clothes/character_clothes24.png"}, - "clothes_26": {name:"clothes_26",img: "resources/customisation/character_clothes/character_clothes25.png"}, - "clothes_27": {name:"clothes_27",img: "resources/customisation/character_clothes/character_clothes26.png"}, - "clothes_28": {name:"clothes_28",img: "resources/customisation/character_clothes/character_clothes27.png"}, - "clothes_29": {name:"clothes_29",img: "resources/customisation/character_clothes/character_clothes28.png"}, - "clothes_30": {name:"clothes_30",img: "resources/customisation/character_clothes/character_clothes29.png"}, - "clothes_31": {name:"clothes_31",img: "resources/customisation/character_clothes/character_clothes30.png"}, - "clothes_32": {name:"clothes_32",img: "resources/customisation/character_clothes/character_clothes31.png"}, - "clothes_33": {name:"clothes_33",img: "resources/customisation/character_clothes/character_clothes32.png"}, - "clothes_34": {name:"clothes_34",img: "resources/customisation/character_clothes/character_clothes33.png"}, - "clothes_35": {name:"clothes_35",img: "resources/customisation/character_clothes/character_clothes34.png"}, - "clothes_36": {name:"clothes_36",img: "resources/customisation/character_clothes/character_clothes35.png"}, - "clothes_37": {name:"clothes_37",img: "resources/customisation/character_clothes/character_clothes36.png"}, - "clothes_38": {name:"clothes_38",img: "resources/customisation/character_clothes/character_clothes37.png"}, - "clothes_39": {name:"clothes_39",img: "resources/customisation/character_clothes/character_clothes38.png"}, - "clothes_40": {name:"clothes_40",img: "resources/customisation/character_clothes/character_clothes39.png"}, - "clothes_41": {name:"clothes_41",img: "resources/customisation/character_clothes/character_clothes40.png"}, - "clothes_42": {name:"clothes_42",img: "resources/customisation/character_clothes/character_clothes41.png"}, - "clothes_43": {name:"clothes_43",img: "resources/customisation/character_clothes/character_clothes42.png"}, - "clothes_44": {name:"clothes_44",img: "resources/customisation/character_clothes/character_clothes43.png"}, - "clothes_45": {name:"clothes_45",img: "resources/customisation/character_clothes/character_clothes44.png"}, - "clothes_46": {name:"clothes_46",img: "resources/customisation/character_clothes/character_clothes45.png"}, - "clothes_47": {name:"clothes_47",img: "resources/customisation/character_clothes/character_clothes46.png"}, - "clothes_48": {name:"clothes_48",img: "resources/customisation/character_clothes/character_clothes47.png"}, - "clothes_49": {name:"clothes_49",img: "resources/customisation/character_clothes/character_clothes48.png"}, - "clothes_50": {name:"clothes_50",img: "resources/customisation/character_clothes/character_clothes49.png"}, - "clothes_51": {name:"clothes_51",img: "resources/customisation/character_clothes/character_clothes50.png"}, - "clothes_52": {name:"clothes_52",img: "resources/customisation/character_clothes/character_clothes51.png"}, - "clothes_53": {name:"clothes_53",img: "resources/customisation/character_clothes/character_clothes52.png"}, - "clothes_54": {name:"clothes_54",img: "resources/customisation/character_clothes/character_clothes53.png"}, - "clothes_55": {name:"clothes_55",img: "resources/customisation/character_clothes/character_clothes54.png"}, - "clothes_56": {name:"clothes_56",img: "resources/customisation/character_clothes/character_clothes55.png"}, - "clothes_57": {name:"clothes_57",img: "resources/customisation/character_clothes/character_clothes56.png"}, - "clothes_58": {name:"clothes_58",img: "resources/customisation/character_clothes/character_clothes57.png"}, - "clothes_59": {name:"clothes_59",img: "resources/customisation/character_clothes/character_clothes58.png"}, - "clothes_60": {name:"clothes_60",img: "resources/customisation/character_clothes/character_clothes59.png"}, - "clothes_61": {name:"clothes_61",img: "resources/customisation/character_clothes/character_clothes60.png"}, - "clothes_62": {name:"clothes_62",img: "resources/customisation/character_clothes/character_clothes61.png"}, - "clothes_63": {name:"clothes_63",img: "resources/customisation/character_clothes/character_clothes62.png"}, - "clothes_64": {name:"clothes_64",img: "resources/customisation/character_clothes/character_clothes63.png"}, - "clothes_65": {name:"clothes_65",img: "resources/customisation/character_clothes/character_clothes64.png"}, - "clothes_66": {name:"clothes_66",img: "resources/customisation/character_clothes/character_clothes65.png"}, - "clothes_67": {name:"clothes_67",img: "resources/customisation/character_clothes/character_clothes66.png"}, - "clothes_68": {name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"}, - "clothes_69": {name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"}, - "clothes_70": {name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"}, - "clothes_pride_shirt": {name:"clothes_pride_shirt",img: "resources/customisation/character_clothes/pride_shirt.png"}, - "clothes_black_hoodie": {name:"clothes_black_hoodie",img: "resources/customisation/character_clothes/black_hoodie.png"}, - "clothes_white_hoodie": {name:"clothes_white_hoodie",img: "resources/customisation/character_clothes/white_hoodie.png"}, - "clothes_engelbert": {name:"clothes_engelbert",img: "resources/customisation/character_clothes/engelbert.png"} + clothes_1: { name: "clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png" }, + clothes_2: { name: "clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png" }, + clothes_3: { name: "clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png" }, + clothes_4: { name: "clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png" }, + clothes_5: { name: "clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png" }, + clothes_6: { name: "clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png" }, + clothes_7: { name: "clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png" }, + clothes_8: { name: "clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png" }, + clothes_9: { name: "clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png" }, + clothes_10: { name: "clothes_10", img: "resources/customisation/character_clothes/character_clothes9.png" }, + clothes_11: { name: "clothes_11", img: "resources/customisation/character_clothes/character_clothes10.png" }, + clothes_12: { name: "clothes_12", img: "resources/customisation/character_clothes/character_clothes11.png" }, + clothes_13: { name: "clothes_13", img: "resources/customisation/character_clothes/character_clothes12.png" }, + clothes_14: { name: "clothes_14", img: "resources/customisation/character_clothes/character_clothes13.png" }, + clothes_15: { name: "clothes_15", img: "resources/customisation/character_clothes/character_clothes14.png" }, + clothes_16: { name: "clothes_16", img: "resources/customisation/character_clothes/character_clothes15.png" }, + clothes_17: { name: "clothes_17", img: "resources/customisation/character_clothes/character_clothes16.png" }, + clothes_18: { name: "clothes_18", img: "resources/customisation/character_clothes/character_clothes17.png" }, + clothes_19: { name: "clothes_19", img: "resources/customisation/character_clothes/character_clothes18.png" }, + clothes_20: { name: "clothes_20", img: "resources/customisation/character_clothes/character_clothes19.png" }, + clothes_21: { name: "clothes_21", img: "resources/customisation/character_clothes/character_clothes20.png" }, + clothes_22: { name: "clothes_22", img: "resources/customisation/character_clothes/character_clothes21.png" }, + clothes_23: { name: "clothes_23", img: "resources/customisation/character_clothes/character_clothes22.png" }, + clothes_24: { name: "clothes_24", img: "resources/customisation/character_clothes/character_clothes23.png" }, + clothes_25: { name: "clothes_25", img: "resources/customisation/character_clothes/character_clothes24.png" }, + clothes_26: { name: "clothes_26", img: "resources/customisation/character_clothes/character_clothes25.png" }, + clothes_27: { name: "clothes_27", img: "resources/customisation/character_clothes/character_clothes26.png" }, + clothes_28: { name: "clothes_28", img: "resources/customisation/character_clothes/character_clothes27.png" }, + clothes_29: { name: "clothes_29", img: "resources/customisation/character_clothes/character_clothes28.png" }, + clothes_30: { name: "clothes_30", img: "resources/customisation/character_clothes/character_clothes29.png" }, + clothes_31: { name: "clothes_31", img: "resources/customisation/character_clothes/character_clothes30.png" }, + clothes_32: { name: "clothes_32", img: "resources/customisation/character_clothes/character_clothes31.png" }, + clothes_33: { name: "clothes_33", img: "resources/customisation/character_clothes/character_clothes32.png" }, + clothes_34: { name: "clothes_34", img: "resources/customisation/character_clothes/character_clothes33.png" }, + clothes_35: { name: "clothes_35", img: "resources/customisation/character_clothes/character_clothes34.png" }, + clothes_36: { name: "clothes_36", img: "resources/customisation/character_clothes/character_clothes35.png" }, + clothes_37: { name: "clothes_37", img: "resources/customisation/character_clothes/character_clothes36.png" }, + clothes_38: { name: "clothes_38", img: "resources/customisation/character_clothes/character_clothes37.png" }, + clothes_39: { name: "clothes_39", img: "resources/customisation/character_clothes/character_clothes38.png" }, + clothes_40: { name: "clothes_40", img: "resources/customisation/character_clothes/character_clothes39.png" }, + clothes_41: { name: "clothes_41", img: "resources/customisation/character_clothes/character_clothes40.png" }, + clothes_42: { name: "clothes_42", img: "resources/customisation/character_clothes/character_clothes41.png" }, + clothes_43: { name: "clothes_43", img: "resources/customisation/character_clothes/character_clothes42.png" }, + clothes_44: { name: "clothes_44", img: "resources/customisation/character_clothes/character_clothes43.png" }, + clothes_45: { name: "clothes_45", img: "resources/customisation/character_clothes/character_clothes44.png" }, + clothes_46: { name: "clothes_46", img: "resources/customisation/character_clothes/character_clothes45.png" }, + clothes_47: { name: "clothes_47", img: "resources/customisation/character_clothes/character_clothes46.png" }, + clothes_48: { name: "clothes_48", img: "resources/customisation/character_clothes/character_clothes47.png" }, + clothes_49: { name: "clothes_49", img: "resources/customisation/character_clothes/character_clothes48.png" }, + clothes_50: { name: "clothes_50", img: "resources/customisation/character_clothes/character_clothes49.png" }, + clothes_51: { name: "clothes_51", img: "resources/customisation/character_clothes/character_clothes50.png" }, + clothes_52: { name: "clothes_52", img: "resources/customisation/character_clothes/character_clothes51.png" }, + clothes_53: { name: "clothes_53", img: "resources/customisation/character_clothes/character_clothes52.png" }, + clothes_54: { name: "clothes_54", img: "resources/customisation/character_clothes/character_clothes53.png" }, + clothes_55: { name: "clothes_55", img: "resources/customisation/character_clothes/character_clothes54.png" }, + clothes_56: { name: "clothes_56", img: "resources/customisation/character_clothes/character_clothes55.png" }, + clothes_57: { name: "clothes_57", img: "resources/customisation/character_clothes/character_clothes56.png" }, + clothes_58: { name: "clothes_58", img: "resources/customisation/character_clothes/character_clothes57.png" }, + clothes_59: { name: "clothes_59", img: "resources/customisation/character_clothes/character_clothes58.png" }, + clothes_60: { name: "clothes_60", img: "resources/customisation/character_clothes/character_clothes59.png" }, + clothes_61: { name: "clothes_61", img: "resources/customisation/character_clothes/character_clothes60.png" }, + clothes_62: { name: "clothes_62", img: "resources/customisation/character_clothes/character_clothes61.png" }, + clothes_63: { name: "clothes_63", img: "resources/customisation/character_clothes/character_clothes62.png" }, + clothes_64: { name: "clothes_64", img: "resources/customisation/character_clothes/character_clothes63.png" }, + clothes_65: { name: "clothes_65", img: "resources/customisation/character_clothes/character_clothes64.png" }, + clothes_66: { name: "clothes_66", img: "resources/customisation/character_clothes/character_clothes65.png" }, + clothes_67: { name: "clothes_67", img: "resources/customisation/character_clothes/character_clothes66.png" }, + clothes_68: { name: "clothes_68", img: "resources/customisation/character_clothes/character_clothes67.png" }, + clothes_69: { name: "clothes_69", img: "resources/customisation/character_clothes/character_clothes68.png" }, + clothes_70: { name: "clothes_70", img: "resources/customisation/character_clothes/character_clothes69.png" }, + clothes_pride_shirt: { + name: "clothes_pride_shirt", + img: "resources/customisation/character_clothes/pride_shirt.png", + }, + clothes_black_hoodie: { + name: "clothes_black_hoodie", + img: "resources/customisation/character_clothes/black_hoodie.png", + }, + clothes_white_hoodie: { + name: "clothes_white_hoodie", + img: "resources/customisation/character_clothes/white_hoodie.png", + }, + clothes_engelbert: { name: "clothes_engelbert", img: "resources/customisation/character_clothes/engelbert.png" }, }; export const HATS_RESOURCES: BodyResourceDescriptionListInterface = { - "hats_1": {name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png"}, - "hats_2": {name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png"}, - "hats_3": {name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png"}, - "hats_4": {name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png"}, - "hats_5": {name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png"}, - "hats_6": {name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png"}, - "hats_7": {name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png"}, - "hats_8": {name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png"}, - "hats_9": {name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png"}, - "hats_10": {name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png"}, - "hats_11": {name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png"}, - "hats_12": {name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png"}, - "hats_13": {name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png"}, - "hats_14": {name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png"}, - "hats_15": {name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png"}, - "hats_16": {name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png"}, - "hats_17": {name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png"}, - "hats_18": {name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png"}, - "hats_19": {name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png"}, - "hats_20": {name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png"}, - "hats_21": {name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png"}, - "hats_22": {name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png"}, - "hats_23": {name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"}, - "hats_24": {name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"}, - "hats_25": {name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"}, - "hats_26": {name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"}, - "tinfoil_hat1": {name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png"} + hats_1: { name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png" }, + hats_2: { name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png" }, + hats_3: { name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png" }, + hats_4: { name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png" }, + hats_5: { name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png" }, + hats_6: { name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png" }, + hats_7: { name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png" }, + hats_8: { name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png" }, + hats_9: { name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png" }, + hats_10: { name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png" }, + hats_11: { name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png" }, + hats_12: { name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png" }, + hats_13: { name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png" }, + hats_14: { name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png" }, + hats_15: { name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png" }, + hats_16: { name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png" }, + hats_17: { name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png" }, + hats_18: { name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png" }, + hats_19: { name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png" }, + hats_20: { name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png" }, + hats_21: { name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png" }, + hats_22: { name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png" }, + hats_23: { name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png" }, + hats_24: { name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png" }, + hats_25: { name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png" }, + hats_26: { name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png" }, + tinfoil_hat1: { name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png" }, }; export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = { - "accessory_1": {name: "accessory_1", img: "resources/customisation/character_accessories/character_accessories1.png"}, - "accessory_2": {name: "accessory_2", img: "resources/customisation/character_accessories/character_accessories2.png"}, - "accessory_3": {name: "accessory_3", img: "resources/customisation/character_accessories/character_accessories3.png"}, - "accessory_4": {name: "accessory_4", img: "resources/customisation/character_accessories/character_accessories4.png"}, - "accessory_5": {name: "accessory_5", img: "resources/customisation/character_accessories/character_accessories5.png"}, - "accessory_6": {name: "accessory_6", img: "resources/customisation/character_accessories/character_accessories6.png"}, - "accessory_7": {name: "accessory_7", img: "resources/customisation/character_accessories/character_accessories7.png"}, - "accessory_8": {name: "accessory_8", img: "resources/customisation/character_accessories/character_accessories8.png"}, - "accessory_9": {name: "accessory_9", img: "resources/customisation/character_accessories/character_accessories9.png"}, - "accessory_10": {name: "accessory_10", img: "resources/customisation/character_accessories/character_accessories10.png"}, - "accessory_11": {name: "accessory_11", img: "resources/customisation/character_accessories/character_accessories11.png"}, - "accessory_12": {name: "accessory_12", img: "resources/customisation/character_accessories/character_accessories12.png"}, - "accessory_13": {name: "accessory_13", img: "resources/customisation/character_accessories/character_accessories13.png"}, - "accessory_14": {name: "accessory_14", img: "resources/customisation/character_accessories/character_accessories14.png"}, - "accessory_15": {name: "accessory_15", img: "resources/customisation/character_accessories/character_accessories15.png"}, - "accessory_16": {name: "accessory_16", img: "resources/customisation/character_accessories/character_accessories16.png"}, - "accessory_17": {name: "accessory_17", img: "resources/customisation/character_accessories/character_accessories17.png"}, - "accessory_18": {name: "accessory_18", img: "resources/customisation/character_accessories/character_accessories18.png"}, - "accessory_19": {name: "accessory_19", img: "resources/customisation/character_accessories/character_accessories19.png"}, - "accessory_20": {name: "accessory_20", img: "resources/customisation/character_accessories/character_accessories20.png"}, - "accessory_21": {name: "accessory_21", img: "resources/customisation/character_accessories/character_accessories21.png"}, - "accessory_22": {name: "accessory_22", img: "resources/customisation/character_accessories/character_accessories22.png"}, - "accessory_23": {name: "accessory_23", img: "resources/customisation/character_accessories/character_accessories23.png"}, - "accessory_24": {name: "accessory_24", img: "resources/customisation/character_accessories/character_accessories24.png"}, - "accessory_25": {name: "accessory_25", img: "resources/customisation/character_accessories/character_accessories25.png"}, - "accessory_26": {name: "accessory_26", img: "resources/customisation/character_accessories/character_accessories26.png"}, - "accessory_27": {name: "accessory_27", img: "resources/customisation/character_accessories/character_accessories27.png"}, - "accessory_28": {name: "accessory_28", img: "resources/customisation/character_accessories/character_accessories28.png"}, - "accessory_29": {name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"}, - "accessory_30": {name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"}, - "accessory_31": {name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"}, - "accessory_32": {name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"}, - "accessory_mate_bottle": {name: "accessory_mate_bottle", img: "resources/customisation/character_accessories/mate_bottle1.png"}, - "accessory_mask": {name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png"} + accessory_1: { + name: "accessory_1", + img: "resources/customisation/character_accessories/character_accessories1.png", + }, + accessory_2: { + name: "accessory_2", + img: "resources/customisation/character_accessories/character_accessories2.png", + }, + accessory_3: { + name: "accessory_3", + img: "resources/customisation/character_accessories/character_accessories3.png", + }, + accessory_4: { + name: "accessory_4", + img: "resources/customisation/character_accessories/character_accessories4.png", + }, + accessory_5: { + name: "accessory_5", + img: "resources/customisation/character_accessories/character_accessories5.png", + }, + accessory_6: { + name: "accessory_6", + img: "resources/customisation/character_accessories/character_accessories6.png", + }, + accessory_7: { + name: "accessory_7", + img: "resources/customisation/character_accessories/character_accessories7.png", + }, + accessory_8: { + name: "accessory_8", + img: "resources/customisation/character_accessories/character_accessories8.png", + }, + accessory_9: { + name: "accessory_9", + img: "resources/customisation/character_accessories/character_accessories9.png", + }, + accessory_10: { + name: "accessory_10", + img: "resources/customisation/character_accessories/character_accessories10.png", + }, + accessory_11: { + name: "accessory_11", + img: "resources/customisation/character_accessories/character_accessories11.png", + }, + accessory_12: { + name: "accessory_12", + img: "resources/customisation/character_accessories/character_accessories12.png", + }, + accessory_13: { + name: "accessory_13", + img: "resources/customisation/character_accessories/character_accessories13.png", + }, + accessory_14: { + name: "accessory_14", + img: "resources/customisation/character_accessories/character_accessories14.png", + }, + accessory_15: { + name: "accessory_15", + img: "resources/customisation/character_accessories/character_accessories15.png", + }, + accessory_16: { + name: "accessory_16", + img: "resources/customisation/character_accessories/character_accessories16.png", + }, + accessory_17: { + name: "accessory_17", + img: "resources/customisation/character_accessories/character_accessories17.png", + }, + accessory_18: { + name: "accessory_18", + img: "resources/customisation/character_accessories/character_accessories18.png", + }, + accessory_19: { + name: "accessory_19", + img: "resources/customisation/character_accessories/character_accessories19.png", + }, + accessory_20: { + name: "accessory_20", + img: "resources/customisation/character_accessories/character_accessories20.png", + }, + accessory_21: { + name: "accessory_21", + img: "resources/customisation/character_accessories/character_accessories21.png", + }, + accessory_22: { + name: "accessory_22", + img: "resources/customisation/character_accessories/character_accessories22.png", + }, + accessory_23: { + name: "accessory_23", + img: "resources/customisation/character_accessories/character_accessories23.png", + }, + accessory_24: { + name: "accessory_24", + img: "resources/customisation/character_accessories/character_accessories24.png", + }, + accessory_25: { + name: "accessory_25", + img: "resources/customisation/character_accessories/character_accessories25.png", + }, + accessory_26: { + name: "accessory_26", + img: "resources/customisation/character_accessories/character_accessories26.png", + }, + accessory_27: { + name: "accessory_27", + img: "resources/customisation/character_accessories/character_accessories27.png", + }, + accessory_28: { + name: "accessory_28", + img: "resources/customisation/character_accessories/character_accessories28.png", + }, + accessory_29: { + name: "accessory_29", + img: "resources/customisation/character_accessories/character_accessories29.png", + }, + accessory_30: { + name: "accessory_30", + img: "resources/customisation/character_accessories/character_accessories30.png", + }, + accessory_31: { + name: "accessory_31", + img: "resources/customisation/character_accessories/character_accessories31.png", + }, + accessory_32: { + name: "accessory_32", + img: "resources/customisation/character_accessories/character_accessories32.png", + }, + accessory_mate_bottle: { + name: "accessory_mate_bottle", + img: "resources/customisation/character_accessories/mate_bottle1.png", + }, + accessory_mask: { name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png" }, }; export const LAYERS: BodyResourceDescriptionListInterface[] = [ @@ -336,9 +442,9 @@ export const LAYERS: BodyResourceDescriptionListInterface[] = [ HAIR_RESOURCES, CLOTHES_RESOURCES, HATS_RESOURCES, - ACCESSORIES_RESOURCES + ACCESSORIES_RESOURCES, ]; export const OBJECTS: BodyResourceDescriptionInterface[] = [ - {name:'teleportation', img:'resources/objects/teleportation.png'}, -]; \ No newline at end of file + { name: "teleportation", img: "resources/objects/teleportation.png" }, +]; diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index 17b7ed0e..4ca3d9ee 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -1,14 +1,12 @@ import Scene = Phaser.Scene; -import type {Character} from "./Character"; +import type { Character } from "./Character"; //todo: improve this WIP export class SpeechBubble { private bubble: Phaser.GameObjects.Graphics; private content: Phaser.GameObjects.Text; - constructor(scene: Scene, player: Character, text: string = "") { - const bubbleHeight = 50; const bubblePadding = 10; const bubbleWidth = bubblePadding * 2 + text.length * 10; @@ -49,15 +47,24 @@ export class SpeechBubble { this.bubble.lineBetween(point2X, point2Y, point3X, point3Y); this.bubble.lineBetween(point1X, point1Y, point3X, point3Y); - this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: '20', color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); + this.content = scene.add.text(0, 0, text, { + fontFamily: "Arial", + fontSize: "20", + color: "#000000", + align: "center", + wordWrap: { width: bubbleWidth - bubblePadding * 2 }, + }); player.add(this.content); const bounds = this.content.getBounds(); - this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); + this.content.setPosition( + this.bubble.x + bubbleWidth / 2 - bounds.width / 2, + this.bubble.y + bubbleHeight / 2 - bounds.height / 2 + ); } destroy(): void { - this.bubble.setVisible(false) //todo find a better way + this.bubble.setVisible(false); //todo find a better way this.bubble.destroy(); this.content.destroy(); } diff --git a/front/src/Phaser/Entity/Sprite.ts b/front/src/Phaser/Entity/Sprite.ts index f2abad52..3ef73e7c 100644 --- a/front/src/Phaser/Entity/Sprite.ts +++ b/front/src/Phaser/Entity/Sprite.ts @@ -1,8 +1,7 @@ export class Sprite extends Phaser.GameObjects.Sprite { - constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: number | string) { super(scene, x, y, texture, frame); scene.sys.updateList.add(this); scene.sys.displayList.add(this); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Game/SoundManager.ts b/front/src/Phaser/Game/SoundManager.ts index 47614fca..cf1c165a 100644 --- a/front/src/Phaser/Game/SoundManager.ts +++ b/front/src/Phaser/Game/SoundManager.ts @@ -4,35 +4,39 @@ import BaseSound = Phaser.Sound.BaseSound; import SoundConfig = Phaser.Types.Sound.SoundConfig; class SoundManager { - private soundPromises : Map> = new Map>(); - public loadSound (loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string) : Promise { - let soundPromise = this.soundPromises.get(soundUrl); + private soundPromises: Map> = new Map>(); + public loadSound(loadPlugin: LoaderPlugin, soundManager: BaseSoundManager, soundUrl: string): Promise { + let soundPromise = this.soundPromises.get(soundUrl); if (soundPromise !== undefined) { return soundPromise; } - soundPromise = new Promise((res) => { - + soundPromise = new Promise((res) => { const sound = soundManager.get(soundUrl); if (sound !== null) { return res(sound); } loadPlugin.audio(soundUrl, soundUrl); - loadPlugin.once('filecomplete-audio-' + soundUrl, () => { + loadPlugin.once("filecomplete-audio-" + soundUrl, () => { res(soundManager.add(soundUrl)); }); loadPlugin.start(); }); - this.soundPromises.set(soundUrl,soundPromise); + this.soundPromises.set(soundUrl, soundPromise); return soundPromise; } - public async playSound(loadPlugin: LoaderPlugin, soundManager : BaseSoundManager, soundUrl: string, config: SoundConfig|undefined) : Promise { - const sound = await this.loadSound(loadPlugin,soundManager,soundUrl); + public async playSound( + loadPlugin: LoaderPlugin, + soundManager: BaseSoundManager, + soundUrl: string, + config: SoundConfig | undefined + ): Promise { + const sound = await this.loadSound(loadPlugin, soundManager, soundUrl); if (config === undefined) sound.play(); else sound.play(config); } - public stopSound(soundManager : BaseSoundManager,soundUrl : string){ + public stopSound(soundManager: BaseSoundManager, soundUrl: string) { soundManager.get(soundUrl).stop(); } } diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index 7c7090b0..44b633ed 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -3,17 +3,23 @@ * It has coordinates and an "activation radius" */ import Sprite = Phaser.GameObjects.Sprite; -import type {GameScene} from "../Game/GameScene"; +import type { GameScene } from "../Game/GameScene"; import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; type EventCallback = (state: unknown, parameters: unknown) => void; export class ActionableItem { - private readonly activationRadiusSquared : number; + private readonly activationRadiusSquared: number; private isSelectable: boolean = false; private callbacks: Map> = new Map>(); - public constructor(private id: number, private sprite: Sprite, private eventHandler: GameScene, private activationRadius: number, private onActivateCallback: (item: ActionableItem) => void) { + public constructor( + private id: number, + private sprite: Sprite, + private eventHandler: GameScene, + private activationRadius: number, + private onActivateCallback: (item: ActionableItem) => void + ) { this.activationRadiusSquared = activationRadius * activationRadius; } @@ -25,8 +31,8 @@ export class ActionableItem { * Returns the square of the distance to the object center IF we are in item action range * OR null if we are out of range. */ - public actionableDistance(x: number, y: number): number|null { - const distanceSquared = (x - this.sprite.x)*(x - this.sprite.x) + (y - this.sprite.y)*(y - this.sprite.y); + public actionableDistance(x: number, y: number): number | null { + const distanceSquared = (x - this.sprite.x) * (x - this.sprite.x) + (y - this.sprite.y) * (y - this.sprite.y); if (distanceSquared < this.activationRadiusSquared) { return distanceSquared; } else { @@ -45,7 +51,7 @@ export class ActionableItem { this.getOutlinePlugin()?.add(this.sprite, { thickness: 2, - outlineColor: 0xffff00 + outlineColor: 0xffff00, }); } @@ -60,8 +66,8 @@ export class ActionableItem { this.getOutlinePlugin()?.remove(this.sprite); } - private getOutlinePlugin(): OutlinePipelinePlugin|undefined { - return this.sprite.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined; + private getOutlinePlugin(): OutlinePipelinePlugin | undefined { + return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined; } /** @@ -78,7 +84,7 @@ export class ActionableItem { } public on(eventName: string, callback: EventCallback): void { - let callbacksArray: Array|undefined = this.callbacks.get(eventName); + let callbacksArray: Array | undefined = this.callbacks.get(eventName); if (callbacksArray === undefined) { callbacksArray = new Array(); this.callbacks.set(eventName, callbacksArray); diff --git a/front/src/Phaser/Items/Computer/computer.ts b/front/src/Phaser/Items/Computer/computer.ts index 8240d904..4665c546 100644 --- a/front/src/Phaser/Items/Computer/computer.ts +++ b/front/src/Phaser/Items/Computer/computer.ts @@ -1,86 +1,91 @@ -import * as Phaser from 'phaser'; -import {Scene} from "phaser"; +import * as Phaser from "phaser"; +import { Scene } from "phaser"; import Sprite = Phaser.GameObjects.Sprite; -import type {ITiledMapObject} from "../../Map/ITiledMap"; -import type {ItemFactoryInterface} from "../ItemFactoryInterface"; -import type {GameScene} from "../../Game/GameScene"; -import {ActionableItem} from "../ActionableItem"; +import type { ITiledMapObject } from "../../Map/ITiledMap"; +import type { ItemFactoryInterface } from "../ItemFactoryInterface"; +import type { GameScene } from "../../Game/GameScene"; +import { ActionableItem } from "../ActionableItem"; import * as tg from "generic-type-guard"; -const isComputerState = - new tg.IsInterface().withProperties({ +const isComputerState = new tg.IsInterface() + .withProperties({ status: tg.isString, - }).get(); + }) + .get(); type ComputerState = tg.GuardedType; let state: ComputerState = { - 'status': 'off' + status: "off", }; export default { preload: (loader: Phaser.Loader.LoaderPlugin): void => { - loader.atlas('computer', '/resources/items/computer/computer.png', '/resources/items/computer/computer_atlas.json'); + loader.atlas( + "computer", + "/resources/items/computer/computer.png", + "/resources/items/computer/computer_atlas.json" + ); }, create: (scene: GameScene): void => { scene.anims.create({ - key: 'computer_off', + key: "computer_off", frames: [ { - key: 'computer', - frame: 'computer_off' - } + key: "computer", + frame: "computer_off", + }, ], frameRate: 10, - repeat: -1 + repeat: -1, }); scene.anims.create({ - key: 'computer_run', + key: "computer_run", frames: [ - { - key: 'computer', - frame: 'computer_on1' - }, - { - key: 'computer', - frame: 'computer_on2' - } - ], + { + key: "computer", + frame: "computer_on1", + }, + { + key: "computer", + frame: "computer_on2", + }, + ], frameRate: 5, - repeat: -1 + repeat: -1, }); }, factory: (scene: GameScene, object: ITiledMapObject, initState: unknown): ActionableItem => { if (initState !== undefined) { if (!isComputerState(initState)) { - throw new Error('Invalid state received for computer object'); + throw new Error("Invalid state received for computer object"); } state = initState; } // Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/ - const computer = new Sprite(scene, object.x, object.y, 'computer'); + const computer = new Sprite(scene, object.x, object.y, "computer"); scene.add.existing(computer); - if (state.status === 'on') { - computer.anims.play('computer_run'); + if (state.status === "on") { + computer.anims.play("computer_run"); } const item = new ActionableItem(object.id, computer, scene, 32, (item: ActionableItem) => { - if (state.status === 'off') { - state.status = 'on'; - item.emit('TURN_ON', state); + if (state.status === "off") { + state.status = "on"; + item.emit("TURN_ON", state); } else { - state.status = 'off'; - item.emit('TURN_OFF', state); + state.status = "off"; + item.emit("TURN_OFF", state); } }); - item.on('TURN_ON', () => { - computer.anims.play('computer_run'); + item.on("TURN_ON", () => { + computer.anims.play("computer_run"); }); - item.on('TURN_OFF', () => { - computer.anims.play('computer_off'); + item.on("TURN_OFF", () => { + computer.anims.play("computer_off"); }); return item; //scene.add.sprite(object.x, object.y, 'computer'); - } + }, } as ItemFactoryInterface; diff --git a/front/src/Phaser/Items/ItemFactoryInterface.ts b/front/src/Phaser/Items/ItemFactoryInterface.ts index deff8fbb..fa7e7459 100644 --- a/front/src/Phaser/Items/ItemFactoryInterface.ts +++ b/front/src/Phaser/Items/ItemFactoryInterface.ts @@ -1,6 +1,6 @@ -import type {GameScene} from "../Game/GameScene"; -import type {ITiledMapObject} from "../Map/ITiledMap"; -import type {ActionableItem} from "./ActionableItem"; +import type { GameScene } from "../Game/GameScene"; +import type { ITiledMapObject } from "../Map/ITiledMap"; +import type { ActionableItem } from "./ActionableItem"; import LoaderPlugin = Phaser.Loader.LoaderPlugin; export interface ItemFactoryInterface { diff --git a/front/src/Phaser/Login/AbstractCharacterScene.ts b/front/src/Phaser/Login/AbstractCharacterScene.ts index 0d3a7c3b..6376498a 100644 --- a/front/src/Phaser/Login/AbstractCharacterScene.ts +++ b/front/src/Phaser/Login/AbstractCharacterScene.ts @@ -1,14 +1,13 @@ -import {ResizableScene} from "./ResizableScene"; -import {localUserStore} from "../../Connexion/LocalUserStore"; -import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; -import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; -import type {CharacterTexture} from "../../Connexion/LocalUser"; +import { ResizableScene } from "./ResizableScene"; +import { localUserStore } from "../../Connexion/LocalUserStore"; +import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; +import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; +import type { CharacterTexture } from "../../Connexion/LocalUser"; export abstract class AbstractCharacterScene extends ResizableScene { - - loadCustomSceneSelectCharacters() : Promise { + loadCustomSceneSelectCharacters(): Promise { const textures = this.getTextures(); - const promises : Promise[] = []; + const promises: Promise[] = []; if (textures) { for (const texture of textures) { if (texture.level === -1) { @@ -17,10 +16,10 @@ export abstract class AbstractCharacterScene extends ResizableScene { promises.push(loadCustomTexture(this.load, texture)); } } - return Promise.all(promises) + return Promise.all(promises); } - loadSelectSceneCharacters() : Promise { + loadSelectSceneCharacters(): Promise { const textures = this.getTextures(); const promises: Promise[] = []; if (textures) { @@ -31,10 +30,10 @@ export abstract class AbstractCharacterScene extends ResizableScene { promises.push(loadCustomTexture(this.load, texture)); } } - return Promise.all(promises) + return Promise.all(promises); } - private getTextures() : CharacterTexture[]|undefined{ + private getTextures(): CharacterTexture[] | undefined { const localUser = localUserStore.getLocalUser(); return localUser?.textures; } diff --git a/front/src/Phaser/Login/ResizableScene.ts b/front/src/Phaser/Login/ResizableScene.ts index d06cb66c..90613744 100644 --- a/front/src/Phaser/Login/ResizableScene.ts +++ b/front/src/Phaser/Login/ResizableScene.ts @@ -1,4 +1,4 @@ -import {Scene} from "phaser"; +import { Scene } from "phaser"; import DOMElement = Phaser.GameObjects.DOMElement; export abstract class ResizableScene extends Scene { @@ -11,13 +11,10 @@ export abstract class ResizableScene extends Scene { * @param defaultWidth The width of the DOM element. We try to compute it but it may not be available if called from "create". */ public centerXDomElement(object: DOMElement, defaultWidth: number): void { - object.x = (this.scale.width / 2) - - ( - object - && object.node - && object.node.getBoundingClientRect().width > 0 - ? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom) - : (defaultWidth / this.scale.zoom) - ); + object.x = + this.scale.width / 2 - + (object && object.node && object.node.getBoundingClientRect().width > 0 + ? object.node.getBoundingClientRect().width / 2 / this.scale.zoom + : defaultWidth / this.scale.zoom); } } diff --git a/front/src/Phaser/Login/SelectCharacterMobileScene.ts b/front/src/Phaser/Login/SelectCharacterMobileScene.ts index 0d8e49d5..c04d6fb3 100644 --- a/front/src/Phaser/Login/SelectCharacterMobileScene.ts +++ b/front/src/Phaser/Login/SelectCharacterMobileScene.ts @@ -1,14 +1,13 @@ import { SelectCharacterScene } from "./SelectCharacterScene"; export class SelectCharacterMobileScene extends SelectCharacterScene { - - create(){ + create() { super.create(); this.onResize(); this.selectedRectangle.destroy(); } - protected defineSetupPlayer(num: number){ + protected defineSetupPlayer(num: number) { const deltaX = 30; const deltaY = 2; let [playerX, playerY] = this.getCharacterPosition(); @@ -16,48 +15,44 @@ export class SelectCharacterMobileScene extends SelectCharacterScene { let playerScale = 1.5; let playerOpacity = 1; - if( this.currentSelectUser !== num ){ + if (this.currentSelectUser !== num) { playerVisible = false; } - if( num === (this.currentSelectUser + 1) ){ + if (num === this.currentSelectUser + 1) { playerY -= deltaY; playerX += deltaX; playerScale = 0.8; playerOpacity = 0.6; playerVisible = true; } - if( num === (this.currentSelectUser + 2) ){ + if (num === this.currentSelectUser + 2) { playerY -= deltaY; - playerX += (deltaX * 2); + playerX += deltaX * 2; playerScale = 0.8; playerOpacity = 0.6; playerVisible = true; } - if( num === (this.currentSelectUser - 1) ){ + if (num === this.currentSelectUser - 1) { playerY -= deltaY; playerX -= deltaX; playerScale = 0.8; playerOpacity = 0.6; playerVisible = true; } - if( num === (this.currentSelectUser - 2) ){ + if (num === this.currentSelectUser - 2) { playerY -= deltaY; - playerX -= (deltaX * 2); + playerX -= deltaX * 2; playerScale = 0.8; playerOpacity = 0.6; playerVisible = true; } - return {playerX, playerY, playerScale, playerOpacity, playerVisible} + return { playerX, playerY, playerScale, playerOpacity, playerVisible }; } - /** + /** * Returns pixel position by on column and row number */ - protected getCharacterPosition(): [number, number] { - return [ - this.game.renderer.width / 2, - this.game.renderer.height / 3 - ]; - } - + protected getCharacterPosition(): [number, number] { + return [this.game.renderer.width / 2, this.game.renderer.height / 3]; + } } diff --git a/front/src/Phaser/Player/Animation.ts b/front/src/Phaser/Player/Animation.ts index ea8d2308..cf13e087 100644 --- a/front/src/Phaser/Player/Animation.ts +++ b/front/src/Phaser/Player/Animation.ts @@ -1,13 +1,10 @@ - export enum PlayerAnimationDirections { - Down = 'down', - Left = 'left', - Up = 'up', - Right = 'right', + Down = "down", + Left = "left", + Up = "up", + Right = "right", } export enum PlayerAnimationTypes { - Walk = 'walk', - Idle = 'idle', + Walk = "walk", + Idle = "idle", } - - diff --git a/front/src/Phaser/Reconnecting/WAError.ts b/front/src/Phaser/Reconnecting/WAError.ts index cdc433b6..abc71f6c 100644 --- a/front/src/Phaser/Reconnecting/WAError.ts +++ b/front/src/Phaser/Reconnecting/WAError.ts @@ -3,13 +3,13 @@ export class WAError extends Error { private _subTitle: string; private _details: string; - constructor (title: string, subTitle: string, details: string) { - super(title+' - '+subTitle+' - '+details); + constructor(title: string, subTitle: string, details: string) { + super(title + " - " + subTitle + " - " + details); this._title = title; this._subTitle = subTitle; this._details = details; // Set the prototype explicitly. - Object.setPrototypeOf (this, WAError.prototype); + Object.setPrototypeOf(this, WAError.prototype); } get title(): string { diff --git a/front/src/Phaser/Services/HdpiManager.ts b/front/src/Phaser/Services/HdpiManager.ts index 33f7e3a8..116f6816 100644 --- a/front/src/Phaser/Services/HdpiManager.ts +++ b/front/src/Phaser/Services/HdpiManager.ts @@ -1,4 +1,3 @@ - interface Size { width: number; height: number; @@ -23,14 +22,14 @@ export class HdpiManager { * * @param realPixelScreenSize */ - public getOptimalGameSize(realPixelScreenSize: Size): { game: Size, real: Size } { + public getOptimalGameSize(realPixelScreenSize: Size): { game: Size; real: Size } { const realPixelNumber = realPixelScreenSize.width * realPixelScreenSize.height; // If the screen has not a definition small enough to match the minimum number of pixels we want to display, // let's make the canvas the size of the screen (in real pixels) if (realPixelNumber <= this.minRecommendedGamePixelsNumber) { return { game: realPixelScreenSize, - real: realPixelScreenSize + real: realPixelScreenSize, }; } @@ -49,8 +48,8 @@ export class HdpiManager { real: { width: realPixelScreenSize.width, height: realPixelScreenSize.height, - } - } + }, + }; } const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier); @@ -58,8 +57,12 @@ export class HdpiManager { // Let's ensure we display a minimum of pixels, even if crazily zoomed in. if (gameWidth * gameHeight < this.absoluteMinPixelNumber) { - const minGameHeight = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.height / realPixelScreenSize.width); - const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height); + const minGameHeight = Math.sqrt( + (this.absoluteMinPixelNumber * realPixelScreenSize.height) / realPixelScreenSize.width + ); + const minGameWidth = Math.sqrt( + (this.absoluteMinPixelNumber * realPixelScreenSize.width) / realPixelScreenSize.height + ); // Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter) this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel; @@ -72,9 +75,8 @@ export class HdpiManager { real: { width: realPixelScreenSize.width, height: realPixelScreenSize.height, - } - } - + }, + }; } return { @@ -85,8 +87,8 @@ export class HdpiManager { real: { width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel, height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel, - } - } + }, + }; } /** @@ -95,7 +97,7 @@ export class HdpiManager { private getOptimalZoomLevel(realPixelNumber: number): number { const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber); if (1.5 <= result && result < 2) { - return 1.5 + return 1.5; } else { return Math.floor(result); } diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts index abfd2a8b..5ceaeb71 100644 --- a/front/src/Phaser/Services/WaScaleManager.ts +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -1,10 +1,9 @@ -import {HdpiManager} from "./HdpiManager"; +import { HdpiManager } from "./HdpiManager"; import ScaleManager = Phaser.Scale.ScaleManager; -import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; -import type {Game} from "../Game/Game"; -import {ResizableScene} from "../Login/ResizableScene"; -import {HtmlUtils} from "../../WebRtc/HtmlUtils"; - +import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; +import type { Game } from "../Game/Game"; +import { ResizableScene } from "../Login/ResizableScene"; +import { HtmlUtils } from "../../WebRtc/HtmlUtils"; class WaScaleManager { private hdpiManager: HdpiManager; @@ -23,26 +22,29 @@ class WaScaleManager { } public applyNewSize() { - const {width, height} = coWebsiteManager.getGameSize(); + const { width, height } = coWebsiteManager.getGameSize(); let devicePixelRatio = 1; if (window.devicePixelRatio) { devicePixelRatio = window.devicePixelRatio; } - const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio}); + const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({ + width: width * devicePixelRatio, + height: height * devicePixelRatio, + }); this.actualZoom = realSize.width / gameSize.width / devicePixelRatio; - this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio) + this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio); this.scaleManager.resize(gameSize.width, gameSize.height); // Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves const style = this.scaleManager.canvas.style; - style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px'; - style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px'; + style.width = Math.ceil(realSize.width / devicePixelRatio) + "px"; + style.height = Math.ceil(realSize.height / devicePixelRatio) + "px"; // Resize the game element at the same size at the canvas - const gameStyle = HtmlUtils.getElementByIdOrFail('game').style; + const gameStyle = HtmlUtils.getElementByIdOrFail("game").style; gameStyle.width = style.width; gameStyle.height = style.height; @@ -70,7 +72,7 @@ class WaScaleManager { this._saveZoom = this.hdpiManager.zoomModifier; } - public restoreZoom(): void{ + public restoreZoom(): void { this.hdpiManager.zoomModifier = this._saveZoom; this.applyNewSize(); } @@ -81,7 +83,6 @@ class WaScaleManager { public get uiScalingFactor(): number { return this.actualZoom > 1 ? 1 : 1.2; } - } -export const waScaleManager = new WaScaleManager(640*480, 196*196); +export const waScaleManager = new WaScaleManager(640 * 480, 196 * 196); diff --git a/front/src/Phaser/UserInput/PinchManager.ts b/front/src/Phaser/UserInput/PinchManager.ts index 3174c6ad..47d5716b 100644 --- a/front/src/Phaser/UserInput/PinchManager.ts +++ b/front/src/Phaser/UserInput/PinchManager.ts @@ -1,6 +1,6 @@ -import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js"; -import {waScaleManager} from "../Services/WaScaleManager"; -import {GameScene} from "../Game/GameScene"; +import { Pinch } from "phaser3-rex-plugins/plugins/gestures.js"; +import { waScaleManager } from "../Services/WaScaleManager"; +import { GameScene } from "../Game/GameScene"; export class PinchManager { private scene: Phaser.Scene; @@ -15,18 +15,18 @@ export class PinchManager { // We are smoothing its value with previous values to prevent the flicking. let smoothPinch = 1; - this.pinch.on('pinchstart', () => { + this.pinch.on("pinchstart", () => { smoothPinch = 1; }); - - this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line + // eslint-disable-next-line + this.pinch.on("pinch", (pinch: any) => { if (pinch.scaleFactor > 1.2 || pinch.scaleFactor < 0.8) { // Pinch too fast! Probably a bad measure. return; } - smoothPinch = 3/5*smoothPinch + 2/5*pinch.scaleFactor; + smoothPinch = (3 / 5) * smoothPinch + (2 / 5) * pinch.scaleFactor; if (this.scene instanceof GameScene) { this.scene.zoomByFactor(smoothPinch); } else { diff --git a/front/src/Stores/ConsoleGlobalMessageManagerStore.ts b/front/src/Stores/ConsoleGlobalMessageManagerStore.ts index 1fa04bfe..01534766 100644 --- a/front/src/Stores/ConsoleGlobalMessageManagerStore.ts +++ b/front/src/Stores/ConsoleGlobalMessageManagerStore.ts @@ -2,4 +2,4 @@ import { writable } from "svelte/store"; export const consoleGlobalMessageManagerVisibleStore = writable(false); -export const consoleGlobalMessageManagerFocusStore = writable(false); \ No newline at end of file +export const consoleGlobalMessageManagerFocusStore = writable(false); diff --git a/front/src/Stores/ErrorStore.ts b/front/src/Stores/ErrorStore.ts index 2f1e3e40..b7fb2e31 100644 --- a/front/src/Stores/ErrorStore.ts +++ b/front/src/Stores/ErrorStore.ts @@ -1,4 +1,4 @@ -import {writable} from "svelte/store"; +import { writable } from "svelte/store"; /** * A store that contains a list of error messages to be displayed. @@ -8,7 +8,7 @@ function createErrorStore() { return { subscribe, - addErrorMessage: (e: string|Error): void => { + addErrorMessage: (e: string | Error): void => { update((messages: string[]) => { let message: string; if (e instanceof Error) { @@ -26,7 +26,7 @@ function createErrorStore() { }, clearMessages: (): void => { set([]); - } + }, }; } diff --git a/front/src/Stores/Errors/BrowserTooOldError.ts b/front/src/Stores/Errors/BrowserTooOldError.ts index bf934443..92409335 100644 --- a/front/src/Stores/Errors/BrowserTooOldError.ts +++ b/front/src/Stores/Errors/BrowserTooOldError.ts @@ -1,8 +1,10 @@ export class BrowserTooOldError extends Error { - static NAME = 'BrowserTooOldError'; + static NAME = "BrowserTooOldError"; constructor() { - super('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.'); + super( + "Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome." + ); this.name = BrowserTooOldError.NAME; } } diff --git a/front/src/Stores/Errors/WebviewOnOldIOS.ts b/front/src/Stores/Errors/WebviewOnOldIOS.ts index 06c03f0e..42370237 100644 --- a/front/src/Stores/Errors/WebviewOnOldIOS.ts +++ b/front/src/Stores/Errors/WebviewOnOldIOS.ts @@ -1,8 +1,10 @@ export class WebviewOnOldIOS extends Error { - static NAME = 'WebviewOnOldIOS'; + static NAME = "WebviewOnOldIOS"; constructor() { - super('Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above.'); + super( + "Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above." + ); this.name = WebviewOnOldIOS.NAME; } } diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index b19a9356..bd32266c 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -4,7 +4,7 @@ import { userMovingStore } from "./GameStore"; import { HtmlUtils } from "../WebRtc/HtmlUtils"; import { BrowserTooOldError } from "./Errors/BrowserTooOldError"; import { errorStore } from "./ErrorStore"; -import { isIOS } from "../WebRtc/DeviceUtils"; +import { getNavigatorType, isIOS, NavigatorType } from "../WebRtc/DeviceUtils"; import { WebviewOnOldIOS } from "./Errors/WebviewOnOldIOS"; import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility"; import { peerStore } from "./PeerStore"; @@ -339,18 +339,19 @@ interface StreamErrorValue { } let currentStream: MediaStream | null = null; +let oldConstraints = { video: false, audio: false }; +//only firefox correctly implements the 'enabled' track property, on chrome we have to stop the track then reinstantiate the stream +const implementCorrectTrackBehavior = getNavigatorType() === NavigatorType.firefox; /** * Stops the camera from filming */ function applyCameraConstraints(currentStream: MediaStream | null, constraints: MediaTrackConstraints | boolean): void { - if (currentStream) { - for (const track of currentStream.getVideoTracks()) { - track.enabled = constraints !== false; - if (constraints && constraints !== true) { - track.applyConstraints(constraints); - } - } + if (!currentStream) { + return; + } + for (const track of currentStream.getVideoTracks()) { + toggleConstraints(track, constraints); } } @@ -361,13 +362,22 @@ function applyMicrophoneConstraints( currentStream: MediaStream | null, constraints: MediaTrackConstraints | boolean ): void { - if (currentStream) { - for (const track of currentStream.getAudioTracks()) { - track.enabled = constraints !== false; - if (constraints && constraints !== true) { - track.applyConstraints(constraints); - } - } + if (!currentStream) { + return; + } + for (const track of currentStream.getAudioTracks()) { + toggleConstraints(track, constraints); + } +} + +function toggleConstraints(track: MediaStreamTrack, constraints: MediaTrackConstraints | boolean): void { + if (implementCorrectTrackBehavior) { + track.enabled = constraints !== false; + } else if (constraints === false) { + track.stop(); + } + if (constraints && constraints !== true) { + track.applyConstraints(constraints); } } @@ -379,6 +389,53 @@ export const localStreamStore = derived, LocalS ($mediaStreamConstraintsStore, set) => { const constraints = { ...$mediaStreamConstraintsStore }; + async function initStream(constraints: MediaStreamConstraints) { + try { + const newStream = await navigator.mediaDevices.getUserMedia(constraints); + if (currentStream) { + //we need stop all tracks to make sure the old stream will be garbage collected + currentStream.getTracks().forEach((t) => t.stop()); + } + currentStream = newStream; + set({ + type: "success", + stream: currentStream, + }); + return; + } catch (e) { + if (constraints.video !== false || constraints.audio !== false) { + console.info( + "Error. Unable to get microphone and/or camera access. Trying audio only.", + constraints, + e + ); + // TODO: does it make sense to pop this error when retrying? + set({ + type: "error", + error: e, + }); + // Let's try without video constraints + if (constraints.video !== false) { + requestedCameraState.disableWebcam(); + } + if (constraints.audio !== false) { + requestedMicrophoneState.disableMicrophone(); + } + } else if (!constraints.video && !constraints.audio) { + set({ + type: "error", + error: new MediaStreamConstraintsError(), + }); + } else { + console.info("Error. Unable to get microphone and/or camera access.", constraints, e); + set({ + type: "error", + error: e, + }); + } + } + } + if (navigator.mediaDevices === undefined) { if (window.location.protocol === "http:") { //throw new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'); @@ -405,57 +462,31 @@ export const localStreamStore = derived, LocalS applyMicrophoneConstraints(currentStream, constraints.audio || false); applyCameraConstraints(currentStream, constraints.video || false); - if (currentStream === null) { - // we need to assign a first value to the stream because getUserMedia is async - set({ - type: "success", - stream: null, - }); - (async () => { - try { - currentStream = await navigator.mediaDevices.getUserMedia(constraints); - set({ - type: "success", - stream: currentStream, - }); - return; - } catch (e) { - if (constraints.video !== false || constraints.audio !== false) { - console.info( - "Error. Unable to get microphone and/or camera access. Trying audio only.", - $mediaStreamConstraintsStore, - e - ); - // TODO: does it make sense to pop this error when retrying? - set({ - type: "error", - error: e, - }); - // Let's try without video constraints - if (constraints.video !== false) { - requestedCameraState.disableWebcam(); - } - if (constraints.audio !== false) { - requestedMicrophoneState.disableMicrophone(); - } - } else if (!constraints.video && !constraints.audio) { - set({ - type: "error", - error: new MediaStreamConstraintsError(), - }); - } else { - console.info( - "Error. Unable to get microphone and/or camera access.", - $mediaStreamConstraintsStore, - e - ); - set({ - type: "error", - error: e, - }); - } - } - })(); + if (implementCorrectTrackBehavior) { + //on good navigators like firefox, we can instantiate the stream once and simply disable or enable the tracks as needed + if (currentStream === null) { + // we need to assign a first value to the stream because getUserMedia is async + set({ + type: "success", + stream: null, + }); + initStream(constraints); + } + } else { + //on bad navigators like chrome, we have to stop the tracks when we mute and reinstantiate the stream when we need to unmute + if (constraints.audio === false && constraints.video === false) { + currentStream = null; + set({ + type: "success", + stream: null, + }); + } else if ((constraints.audio && !oldConstraints.audio) || (!oldConstraints.video && constraints.video)) { + initStream(constraints); + } + oldConstraints = { + video: !!constraints.video, + audio: !!constraints.audio, + }; } } ); diff --git a/front/src/Stores/MenuStore.ts b/front/src/Stores/MenuStore.ts index 035ba174..96e731a9 100644 --- a/front/src/Stores/MenuStore.ts +++ b/front/src/Stores/MenuStore.ts @@ -71,7 +71,7 @@ function createSubMenusStore() { export const subMenusStore = createSubMenusStore(); -function checkSubMenuToShow() { +export function checkSubMenuToShow() { if (!get(userIsAdminStore)) { subMenusStore.removeMenu(SubMenusInterface.globalMessages); } @@ -81,8 +81,6 @@ function checkSubMenuToShow() { } } -checkSubMenuToShow(); - export const customMenuIframe = new Map(); export function handleMenuRegistrationEvent( diff --git a/front/src/Stores/SelectCharacterStore.ts b/front/src/Stores/SelectCharacterStore.ts index 094eaef3..0c84a031 100644 --- a/front/src/Stores/SelectCharacterStore.ts +++ b/front/src/Stores/SelectCharacterStore.ts @@ -1,3 +1,3 @@ import { derived, writable, Writable } from "svelte/store"; -export const selectCharacterSceneVisibleStore = writable(false); \ No newline at end of file +export const selectCharacterSceneVisibleStore = writable(false); diff --git a/front/src/Stores/SoundPlayingStore.ts b/front/src/Stores/SoundPlayingStore.ts index cf1d681c..36fd5d77 100644 --- a/front/src/Stores/SoundPlayingStore.ts +++ b/front/src/Stores/SoundPlayingStore.ts @@ -4,7 +4,7 @@ import { writable } from "svelte/store"; * A store that contains the URL of the sound currently playing */ function createSoundPlayingStore() { - const { subscribe, set, update } = writable(null); + const { subscribe, set, update } = writable(null); return { subscribe, @@ -13,9 +13,7 @@ function createSoundPlayingStore() { }, soundEnded: () => { set(null); - } - - + }, }; } diff --git a/front/src/Touch/TouchScreenManager.ts b/front/src/Touch/TouchScreenManager.ts index dcb56ded..ee2a135a 100644 --- a/front/src/Touch/TouchScreenManager.ts +++ b/front/src/Touch/TouchScreenManager.ts @@ -1,16 +1,14 @@ - class TouchScreenManager { - - readonly supportTouchScreen:boolean; - + readonly supportTouchScreen: boolean; + constructor() { this.supportTouchScreen = this.detectTouchscreen(); } //found here: https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript#4819886 detectTouchscreen(): boolean { - return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)); + return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; } } -export const touchScreenManager = new TouchScreenManager(); \ No newline at end of file +export const touchScreenManager = new TouchScreenManager(); diff --git a/front/src/WebRtc/DeviceUtils.ts b/front/src/WebRtc/DeviceUtils.ts index 91a3dc31..9be07501 100644 --- a/front/src/WebRtc/DeviceUtils.ts +++ b/front/src/WebRtc/DeviceUtils.ts @@ -1,12 +1,29 @@ export function isIOS(): boolean { - return [ - 'iPad Simulator', - 'iPhone Simulator', - 'iPod Simulator', - 'iPad', - 'iPhone', - 'iPod' - ].includes(navigator.platform) + return ( + ["iPad Simulator", "iPhone Simulator", "iPod Simulator", "iPad", "iPhone", "iPod"].includes( + navigator.platform + ) || // iPad on iOS 13 detection - || (navigator.userAgent.includes("Mac") && "ontouchend" in document) + (navigator.userAgent.includes("Mac") && "ontouchend" in document) + ); +} + +export enum NavigatorType { + firefox = 1, + chrome, + safari, +} + +export function getNavigatorType(): NavigatorType { + if (window.navigator.userAgent.includes("Firefox")) { + return NavigatorType.firefox; + } else if (window.navigator.userAgent.includes("Chrome")) { + return NavigatorType.chrome; + } else if (window.navigator.userAgent.includes("Safari")) { + return NavigatorType.safari; + } + throw "Couldn't detect navigator type"; +} +export function isAndroid(): boolean { + return window.navigator.userAgent.includes("Android"); } diff --git a/front/src/rex-plugins.d.ts b/front/src/rex-plugins.d.ts index 8c8a9fc1..9884d425 100644 --- a/front/src/rex-plugins.d.ts +++ b/front/src/rex-plugins.d.ts @@ -1,16 +1,16 @@ -declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' { +declare module "phaser3-rex-plugins/plugins/virtualjoystick.js" { const content: any; // eslint-disable-line export default content; } -declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' { +declare module "phaser3-rex-plugins/plugins/gestures-plugin.js" { const content: any; // eslint-disable-line export default content; } -declare module 'phaser3-rex-plugins/plugins/webfontloader-plugin.js' { +declare module "phaser3-rex-plugins/plugins/webfontloader-plugin.js" { const content: any; // eslint-disable-line export default content; } -declare module 'phaser3-rex-plugins/plugins/outlinepipeline-plugin.js' { +declare module "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js" { import GameObject = Phaser.GameObjects.GameObject; class OutlinePipelinePlugin { @@ -21,6 +21,6 @@ declare module 'phaser3-rex-plugins/plugins/outlinepipeline-plugin.js' { export default OutlinePipelinePlugin; } -declare module 'phaser3-rex-plugins/plugins/gestures.js' { +declare module "phaser3-rex-plugins/plugins/gestures.js" { export const Pinch: any; // eslint-disable-line } diff --git a/front/style/TextGlobalMessageSvelte-Style.scss b/front/style/TextGlobalMessageSvelte-Style.scss index ce67b699..dda65d05 100644 --- a/front/style/TextGlobalMessageSvelte-Style.scss +++ b/front/style/TextGlobalMessageSvelte-Style.scss @@ -1,10 +1,10 @@ //TextGlobalMessage section.section-input-send-text { - --height-toolbar: 15%; + --height-toolbar: 20%; height: 100%; .ql-toolbar{ - height: var(--height-toolbar); + max-height: var(--height-toolbar); background: whitesmoke; }