Compare commits

...

106 Commits

Author SHA1 Message Date
Florian Rival
13b62a06eb Add option to allow building Android App Bundles that can be uploaded if you already published APKs with Play App Signing enabled for your game
* If you already opted in for "Play App Signing" in the Play Developer Console and published some APKs like this, you need to enable the new "Old upload key" option, in the Signing Option, before *each* packaging for Android.
* Read this [page to learn more](http://wiki.compilgames.net/doku.php/gdevelop5/publishing/android_and_ios/play-store/upgrading-from-apk-to-aab#done_you_can_now_upload_your_aab) and have step by step details of what to do to update your game.
* If you publish a new game with Android App Bundles, there is nothing to do!
2021-07-22 15:09:58 +01:00
Florian Rival
d27119d8ea Remove deprecated examples upload script from web-app deployment
Don't show in changelog
2021-07-21 17:25:40 +01:00
Florian Rival
dd3ff554d5 Upgrade AppVeyor CI to Node.js 14 (#2826)
[ci skip] [skip ci]
2021-07-20 23:36:56 +01:00
Florian Rival
287a17b634 Add a maximum size for the GDevelop logo so that it's not too big on large resolutions 2021-07-20 18:13:42 +01:00
Florian Rival
7dc477e29e Bump newIDE version 2021-07-20 15:53:20 +01:00
Florian Rival
8174bca3b1 Fix progress bar not shown if GDevelop logo was hidden
Don't show in changelog
2021-07-19 21:41:24 +01:00
Florian Rival
7f6388c6f5 Make the loading screen customizable (#2814)
* Allow to choose the style of the GDevelop logo (light, dark, colored or plain)
* Allow to choose the background color and an optional background image
* Allow to choose the size of the progress bar (with a minimum and maximum size)
* Allow to choose the duration of the fade in animations and the minimum time the loading screen is shown
* Use the **new preview button in the game properties** dialog to run a preview with the loading screen shown
2021-07-19 19:52:25 +01:00
Florian Rival
da3a099ff2 Fix increasing size of the project file whenever changes on a layer were cancelled
* This was because of a duplication of the "cameras" stored in the layer.
* Also add a check to clean project files with the problem.

Fix #2812
2021-07-17 11:56:04 +01:00
Florian Rival
7b2c7b4a00 Bump newIDE version 2021-07-15 18:06:49 +01:00
Florian Rival
f750a356c3 Add missing Flow annotations
Don't show in changelog
2021-07-15 14:50:02 +01:00
Florian Rival
611a72aee1 Fix focus not set when editing a mouse button field in the events sheet
* Also fix various missing Flow annotations
2021-07-14 22:46:18 +01:00
Florian Rival
1b965b65a4 Fix variables not shown properly in the debugger
* This removes toJSON method on gdjs.Variable. This was just introduced in beta 111, if you're using it, please use toJSObject instead and JSON.stringify.
2021-07-14 22:08:00 +01:00
Florian Rival
c077628eb4 Bump newIDE version 2021-07-14 18:12:06 +01:00
Leo_Red
f1a6da0cb2 Remove example issues auto close workflow (#2806)
Don't show in changelog
2021-07-14 14:42:35 +01:00
Aurélien Vivet
df4a780311 Give more consistency to the order of the buttons in the behaviors editor (#2803) 2021-07-13 22:48:58 +01:00
Florian Rival
8c955cf77a Remove useless "Behavior" text in the behaviors editor
Don't show in changelog
2021-07-13 16:15:51 +01:00
Florian Rival
17efae037c Fix crash when analyzing unused variables when a scene is depending on another scene 2021-07-13 16:09:01 +01:00
Florian Rival
f1120238ec Slightly improve rendering scheduling for games 2021-07-13 13:51:32 +01:00
Florian Rival
5d6a2bb3a0 Fix GDJS tests (regression since rework of GDJS build in the editor)
Don't show in changelog
2021-07-13 13:46:32 +01:00
Florian Rival
f3a49ad2cf Speed up GDJS build (including at editor startup) in development
This reduces the number of copied files (let esbuild build directly in the proper output folder).
Also avoid copying the source files after every change (avoiding ~3-4 seconds of copy).

Overall, a change in GDJS/Extensions results in 1-3 seconds of build, instead of almost 10 sometimes due to the large number of file copies.

Only show in developer changelog
2021-07-12 18:11:17 +01:00
Florian Rival
aac3379f59 Remove more deprecated strings from translations
Don't show in changelog
2021-07-12 14:30:28 +01:00
Florian Rival
49949189cf Remove deprecated strings from translations
Don't show in changelog
2021-07-12 11:50:29 +01:00
Florian Rival
977daa50fa Remove gcc from Travis CI matrix as too costly in build minutes
Don't show in changelog
2021-07-11 18:21:48 +01:00
Florian Rival
fd8a7df868 Fix wording 2021-07-11 17:16:04 +01:00
Florian Rival
303110ade5 Fix long description not shown for Yes/No and True/False parameter fields
Fix #2794
2021-07-11 17:16:04 +01:00
Florian Rival
cb15111d03 Display behavior icons in the behaviors editor
* Also better align some icons
* Show button to open the help page for all behaviors
2021-07-11 17:16:04 +01:00
Florian Rival
9db4d603a0 Refactor access to behavior/behavior shared data in the editor
Avoid having getters in the gd::Platform or the gd::Project, as they should not be responsible for this.
Instead, use gd::MetadataProvider to fetch the BehaviorMetadata.

Don't show in changelog
2021-07-11 17:16:04 +01:00
Florian Rival
6c7db9a948 Display extension icons in the project manager 2021-07-11 14:26:57 +01:00
Florian Rival
6b9a8f14e3 Center images in extension generated documentation pages
Don't show in changelog
2021-07-10 18:51:31 +01:00
Florian Rival
2f3a18046a Fix rendering of lists and author names in extensions generated documentation
Don't show in changelog
2021-07-10 18:46:17 +01:00
Florian Rival
812ff43905 Automatically generate pages on the wiki for the community extensions 2021-07-10 18:19:02 +01:00
Florian Rival
1a113004be Update extensions registry loading to use the GDevelop asset API instead of using GitHub directly
Don't show in changelog
2021-07-10 14:18:26 +01:00
Florian Rival
0dd10a7955 Improve extract changelog script
Don't show in changelog
2021-07-09 23:05:27 +01:00
Florian Rival
00b25461ed Fix "X" and "Y" expression for sprite objects (regression)
The default point being passed was incorrectly set as 0 instead of "" (empty string), which was misunderstood by the game engine as the origin point (which would be flipped)

Don't show in changelog
2021-07-09 15:32:48 +01:00
Florian Rival
9e28068e4f Fix Debugger Tools extension not available in the web-app 2021-07-08 20:01:07 +01:00
D8H
6b231f3eb6 Fix: instances editor no longer give unnecessary floating positions (#2790)
* Fix: Moving instances and resizing one instance no longer give floating positions or dimensions on 0° rotated instances.
Don't show in changelog
2021-07-07 20:12:14 +02:00
Florian Rival
2dd2c85f12 Update translations 2021-07-06 17:20:43 +01:00
Florian Rival
c61faa7e05 Clean up some dead code
Don't show in changelog
2021-07-06 17:13:00 +01:00
Florian Rival
1840f6f45a Save a bit of time at startup by avoiding to pre-fetch resources
Don't show in changelog
2021-07-06 15:54:12 +01:00
Florian Rival
b11eccfa22 Clarify in the list of effects that Kawase blur should be preferred over the classic Gaussian blur for performance 2021-07-06 15:53:37 +01:00
Florian Rival
9236608502 Fix broken sortable list of animations (regression)
Fix #2784

Don't show in changelog
2021-07-06 15:32:35 +01:00
Florian Rival
12a6bc23c3 Add an option to package games as Android App Bundles, to publish them on Google Play
* Starting from August, it will be mandatory to upload Android App Bundles to Google Play - APK files won't be accepted anymore.
2021-07-04 19:29:13 +01:00
Florian Rival
23c2b84707 Fix animation scrolling in the Sprite object editor on small screens 2021-07-03 13:00:32 +01:00
Florian Rival
adb4f2a6cf Fix animation editor preview speed being too fast on monitors with high refresh rate
* Also fix scrolling/zooming in the preview

Fix #2779
2021-07-03 12:03:18 +01:00
D8H
aa3ed78eda Improve selection in the scene editor so that multiple instances can be properly rotated and resized (#2751)
* When resizing or rotating multiple instances, they will keep their relative positions.
* When resizing rotated instances, their aspect ratio will be kept to avoid an unintuitive resize effect 
* This makes it easier to edit chunks of levels, scale them, resize them and rotate them.
2021-07-01 23:26:26 +01:00
Aurélien Vivet
efb8668077 Allow popups in the events sheet to be validated after a change using Ctrl+Enter (or Cmd+Enter on macOS) (#2569)
* Also add a preference to choose if Escape should cancel any changes made (like in a dialog) or not (like previously).
2021-06-30 22:03:19 +01:00
Florian Rival
8d763d4600 Put the button to restore the default collision mask in a split menu
This is because its usage is less frequent, so it does not eat the bottom space.
We can also now delete all polygons to restore the default masks, so its usage will decrease.
2021-06-25 14:11:30 +02:00
Aurélien Vivet
0cc1e57450 Put the button "add a behavior to the object" at the bottom of the list of behaviors (#2760)
* Also restore the default collision mask if all polygons are removed in the custom mask
2021-06-25 13:47:01 +02:00
Florian Rival
24c13e1022 Make the buttons at the bottom of the list of animations always visible in the Sprite editor 2021-06-22 11:52:52 +02:00
Florian Rival
965f7aab32 Fix getting the length of an object array always returning 0 2021-06-22 10:16:43 +02:00
Florian Rival
23c1f2423d Update badge to use travis-ci.com
Don't show in changelog
2021-06-17 09:30:49 +02:00
Arthur Pacaud
1d5eb8e529 Move JSON and JS variable conversions to gdjs.Variable (#2740)
Only show in developer changelog
2021-06-16 16:59:12 +01:00
D8H
8968ef34e2 Add Flow typing to the instances editor (#2745)
Only show in developer changelog
2021-06-16 16:50:51 +01:00
Arthur Pacaud
736ba023ff Add various DX improvements (#2741)
Only show in developer changelog
2021-06-16 16:22:30 +01:00
D8H
ade075f076 Reorganize pathfinding behavior actions and conditions in 2 groups (#2738) 2021-06-16 14:58:47 +01:00
Florian Rival
049c36b081 Fix tests 2021-06-16 08:44:46 +02:00
Florian Rival
3977e2b6f2 Fix formatting 2021-06-14 15:12:11 +01:00
Aurélien Vivet
f08f38bc3b Fix "NAN" shown for the cursor position when opening the scene editor (#2743) 2021-06-14 15:10:53 +01:00
D8H
127a881416 Add a condition and expression to check the movement angle for the pathfinding behavior (#2737) 2021-06-12 18:39:31 +01:00
Florian Rival
606143ff36 Fix typings 2021-06-12 15:53:17 +01:00
Florian Rival
05a68cb17f Fix behavior not always set correctly when editing parameters using the inline popover in the events sheet 2021-06-12 15:52:01 +01:00
Florian Rival
280377cc98 Fix RGB colors with a component at 0 not applied to the ambient light color
Fix #2734
2021-06-12 15:02:49 +01:00
Florian Rival
3c88c150d5 Fix effects container of an object not copied properly (and so erased when previewing)
This is a good example of why custom assignment ctor/copy operator should be avoided. In the case of gd::Object, it would have been better to encapsulate the behaviors in a "gd::BehaviorContentContainer", that would have custom assignement ctor/copy operator, so that we could then avoid specifying those for gd::Object.

Don't show in changelog
2021-06-12 02:08:34 +01:00
D8H
13471ad40a Add actions/conditions/expressions to specify an offset for the virtual grid used by the Pathfinding behavior (#2730) 2021-06-12 00:14:24 +01:00
Florian Rival
0d7d574f1b Fix action and condition to change the movement angle offset of the Top Down Movement behavior 2021-06-12 00:05:02 +01:00
D8H
19a10d7dab Add autocompletions for animation and points of Sprite objects (#2722)
* Both for the parameters of actions and inside expressions.
2021-06-11 23:27:44 +01:00
Arthur Pacaud
84e096f85d Update the IDE to Firebase v9 (to reduce the size of the Firebase module) (#2724)
Only show in developer changelog
2021-06-11 23:13:50 +01:00
Aurélien Vivet
40f5eb3e23 Remove unused argument (#2731)
Don't show in changelog
2021-06-11 11:17:00 +01:00
Arthur Pacaud
4948e16dd5 Add disconnection actions to the p2p extension (#2723) 2021-06-09 22:45:20 +01:00
D8H
2b38325d96 Ensure variables referenced in external events are shown in the autocompletions in the variables editor (#2717) 2021-06-09 21:29:20 +01:00
D8H
9a10caeeac Add autocompletion for variable names in expressions (#2692) 2021-06-07 09:16:12 +01:00
Florian Rival
a4be56a24b Add links when creating issues 2021-06-06 17:47:57 +01:00
Arthur Pacaud
fc8b724e3c Export extension dependencies exported when the extension is used (#2671)
* This for example allow to export the vibration extension only if it's used in events.

Only show in developer changelog
2021-06-06 16:45:47 +01:00
Aurélien Vivet
7e61419a36 Clean the examples folder (#2713)
Only show in developer changelog
2021-06-06 15:37:47 +01:00
Florian Rival
473661b8a2 Fix typings 2021-06-06 15:36:01 +01:00
Florian Rival
350fea08bf Enable serialization of effects in gd::Object
Don't show in changelog
2021-06-05 16:55:28 +01:00
D8H
4f9154549b Add autocompletion in expressions for scene names, layer names and parameters with choices (#2702) 2021-06-05 14:27:28 +01:00
Florian Rival
d9172ded36 Replace the list of examples by an "Example Library" (#2697)
* You can contribute to the examples library by submitting your new (or improved) examples on https://github.com/GDevelopApp/GDevelop-examples
2021-06-05 00:57:23 +01:00
Aurélien Vivet
aef8271a4e Specify type of variables in the events sheet sentences (#2701) 2021-06-02 21:33:29 +01:00
Harsimran Singh Virk
83db8ce272 Added Effects Container in gd::Object (#2683)
Only show in developer changelog
2021-05-31 22:22:08 +01:00
Florian Rival
e26c1470c1 Upgrade to Flow 0.131 (#2695)
Don't show in changelog
2021-05-31 22:19:13 +01:00
Florian Rival
4b382cb191 Allow to pass the path to GDJS to the extensions loader (#2694)
Useful for third party/external tools.

Don't show in changelog
2021-05-30 19:21:42 +01:00
Aurélien Vivet
6c631410f2 Fix the opacity value for BBText and Bitmap Text object, value is now clamped between 0 and 255 (#2693) 2021-05-30 18:44:04 +02:00
D8H
516b1d978c Fix platformer objects so that they can always grab a platform ledge even when multiple platforms are overlapping (#2686) 2021-05-28 22:09:50 +01:00
Arthur Pacaud
822146e5f1 Add an action to add a document in Firestore without having to specify a name (#2687) 2021-05-27 21:58:26 +01:00
Florian Rival
7b3987bae4 Upgrade TypeScript to version 4.3.2 for GDJS game engine (#2688)
Only show in developer changelog
2021-05-27 12:37:54 +01:00
Aurélien Vivet
506c029690 Fix events sheet showing "unknown instruction" when the action/condition editor is dismissed with a backdrop click (#2684) 2021-05-27 09:36:09 +01:00
Aurélien Vivet
b28e4e1230 Hide preferences in the web-app when they are not applicable for it (#2681) 2021-05-26 15:44:36 +01:00
Arthur Pacaud
c1edcbfa19 Add support for running queries in Firebase (#2610) 2021-05-26 09:48:16 +01:00
D8H
3125956dfc In the variables editor, add autocompletion for variables that are used in events but not yet defined in the list (#2665)
* This means that if you use a scene, global or object variable in your events, it will be suggested the next time you open the variables editor of the scene, project or of an object, when you enter the name of a new variable.
* Note that this works only for now for variables that are not arrays or structures.
2021-05-25 23:37:25 +01:00
Florian Rival
633c7bbd9d Fix color field warning
Don't show in changelog
2021-05-25 20:51:03 +01:00
Florian Rival
df92ea1ac9 Add a warning when trying to enter a color text with an extra quote
* See as an example: #2675
2021-05-25 20:41:04 +01:00
Aurélien Vivet
3eb571a03a Fix AABB and the positioning of the bitmap text object. 2021-05-25 20:57:41 +02:00
Aurélien Vivet
98303f7f50 Fix BitmapText height when the text is finishing with new lines (#2670) 2021-05-25 08:44:40 +01:00
Arthur Pacaud
34dfbbc294 Remove misleading descriptions of subscriptions (#2544)
Don't show in changelog
2021-05-24 21:49:32 +02:00
Harsimran Singh Virk
afbb316bd2 Refactor gd::Layer to use a gd::EffectsContainer (#2645)
Only show in developer changelog
2021-05-22 00:22:58 +01:00
Arthur Pacaud
b75945869c Add autocompletions for scene, layers and some parameter fields (#2663)
* Both display the scene names (or layer names, or valid choices, according to the field) and the autocompletions like a usual text expression - making it more obvious that you can use an expression here.
2021-05-22 00:07:34 +01:00
Aurélien Vivet
efb3f55885 Add a preference to display the preview window always on top of other windows (#2667) 2021-05-20 23:14:42 +02:00
Aurélien Vivet
82d2a4b5e4 Add padding option for effects on layers (#2658) 2021-05-18 16:25:01 +01:00
aphoenixholland
7748f3c48e Add support for tweening object color using Hue/Saturation/Lightness (HSL) (#2571)
* This is useful to make some effects or have a more natural color change.
2021-05-17 23:23:03 +01:00
D8H
80ddc9c1b7 Fix grid snapping at the object creation when the grid position is not an integer (#2648) 2021-05-15 20:22:50 +01:00
Arthur Pacaud
5d50c31e55 Add code snippets for Visual Studio Code (#2647)
Only show in developer changelog
2021-05-15 19:05:56 +01:00
Florian Rival
61a969fdb1 Bump newIDE version 2021-05-13 19:42:05 +01:00
Florian Rival
a14b8e8bcd Merge branch 'master' of github.com:4ian/GDevelop 2021-05-13 19:41:51 +01:00
D8H
faa37a4fd2 Update Link Tools examples to use the new link action (#2646) 2021-05-13 19:21:00 +01:00
Florian Rival
176efafe43 Increase the size before warning in the service worker
Don't show in changelog
2021-05-12 23:30:49 +01:00
2527 changed files with 11571 additions and 477872 deletions

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: GDevelop Discord
url: https://discord.gg/rjdYHvj
about: Discuss on the forum or on the Discord to get help creating a game or identifying a bug.
- name: GDevelop Forums
url: https://forum.gdevelop-app.com
about: You can also discuss game creation, new feature requests and bugs on the forum.
- name: GDevelop Roadmap
url: https://trello.com/b/qf0lM7k8/gdevelop-roadmap
about: Look at the roadmap and vote on features that you want to see in GDevelop.
- name: Submit a new game example that you created
url: https://github.com/GDevelopApp/GDevelop-examples/issues/new/choose
about: You can submit a game that you made to be added to examples in the "GDevelop-examples" repository
- name: Submit a new game extension that you created
url: https://github.com/4ian/GDevelop-extensions/issues/new/choose
about: You can submit an extension that you made in the "GDevelop-extensions" repository

View File

@@ -4,13 +4,6 @@ jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Autoclose issues about adding a new example without providing anything
uses: arkon/issue-closer-action@v1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
type: "body"
regex: ".*INSERT the link to your game here, or add it as an attachment.*"
message: "Hi @${issue.user.login}! 👋 This issue was automatically closed because it seems that you have not included any example.\n\nGitHub is a place for the technical development of GDevelop itself - you may want to go on the [forum](https://forum.gdevelop-app.com/), the Discord chat or [read the documentation](http://wiki.compilgames.net/doku.php/gdevelop5/start) to learn more about GDevelop. Thanks!"
- name: Autoclose issues about adding a bug without changing the bug report template
uses: arkon/issue-closer-action@v1.1
with:

View File

@@ -1,7 +1,6 @@
language: cpp
sudo: false
compiler:
- gcc
- clang
# Cache .npm folder for faster npm install

129
.vscode/GDevelopExtensions.code-snippets vendored Normal file
View File

@@ -0,0 +1,129 @@
{
"Define extension": {
"scope": "javascript",
"description": "Adds the boilerplate code of a GDevelop extension definition.",
"prefix": "gdext",
"body": [
"// @flow",
"/**",
" * This is a declaration of an extension for GDevelop 5.",
" *",
" * Changes in this file are watched and automatically imported if the editor",
" * is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).",
" *",
" * The file must be named \"JsExtension.js\", otherwise GDevelop won't load it.",
" * ⚠️ If you make a change and the extension is not loaded, open the developer console",
" * and search for any errors.",
" *",
" * More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md",
" */",
"",
"/*::",
"// Import types to allow Flow to do static type checking on this file.",
"// Extensions declaration are typed using Flow (like the editor), but the files",
"// for the game engine are checked with TypeScript annotations.",
"import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'",
"*/",
"",
"module.exports = {",
"\tcreateExtension: function (",
"\t\t/*: (string) => string */,",
"\t\tgd /*: libGDevelop */",
"\t) {",
"\t\tconst extension = new gd.PlatformExtension();",
"\t\textension",
"\t\t\t.setExtensionInformation(",
"\t\t\t\t'${1:${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/$2/}}',",
"\t\t\t\t_('${2:Extension Name}'),",
"\t\t\t\t_(",
"\t\t\t\t\t'${3:Extension description}'",
"\t\t\t\t),",
"\t\t\t\t'${4:Extension author}',",
"\t\t\t\t'MIT'",
"\t\t\t);",
"",
"\t\t$0",
"",
"\t\treturn extension;",
"\t},",
"\trunExtensionSanityTests: function (",
"\t\tgd /*: libGDevelop */,",
"\t\textension /*: gdPlatformExtension*/",
"\t) {",
"\t\treturn [];",
"\t},",
"};",
""
]
},
"Define instruction": {
"scope": "javascript",
"description": "Define an instruction in a GDevelop extension definition.",
"prefix": "gdinstr",
"body": [
"extension",
"\t.add${1|Condition,Action|}(",
"\t\t'${2:InstructionInternalName}',",
"\t\t_('${3:Instruction full name}'),",
"\t\t_(",
"\t\t\t'${4:Instruction description}'",
"\t\t),",
"\t\t_('${5:Event sheet sentence}'),",
"\t\t_('${6:Events group}'),",
"\t\t'JsPlatform/Extensions/${8:icon}.png',",
"\t\t'JsPlatform/Extensions/${8:icon}.png'",
"\t)",
"\t.getCodeExtraInformation()",
"\t.setIncludeFile('Extensions/${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/$2/}/${9:${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/${2:/downcase}/}tools}.js')",
"\t.setFunctionName('gdjs.evtTools.${7:${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/${2:/downcase}/}.}');",
"",
"$0"
]
},
"Define expression": {
"scope": "javascript",
"description": "Define an expression in a GDevelop extension definition.",
"prefix": "gdexp",
"body": [
"extension",
"\t.add${1|Expression,StrExpression|}(",
"\t\t'${2:ExpressionsInternalName}',",
"\t\t_('${3:Expression full name}'),",
"\t\t_(",
"\t\t\t'${4:Expression description}'",
"\t\t),",
"\t\t_('${5:Events group}'),",
"\t\t'JsPlatform/Extensions/${7:icon}.png'",
"\t)",
"\t.getCodeExtraInformation()",
"\t.setIncludeFile('Extensions/${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/$2/}/${8:${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/${2:/downcase}/}tools}.js')",
"\t.setFunctionName('gdjs.evtTools.${6:${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/${2:/downcase}/}.}');",
"",
"$0"
]
},
"Add parameter": {
"scope": "javascript",
"description": "Define a parameter in a GDevelop extension definition.",
"prefix": "gdparam",
"body": [
".addParameter('${1|string,expression,object,behavior,yesorno,stringWithSelector,scenevar,globalvar,objectvar,objectList,objectListWithoutPicking,color,key,sceneName,file,layer,relationalOperator,operator,trueorfalse,musicfile,soundfile,police,mouse,passwordjoyaxis,camera,objectPtr,forceMultiplier|}', '${2:Parameter description}', '${3:Optional parameter data}', /*parameterIsOptional=*/${4|false,true|})"
]
},
"Add code only parameter": {
"scope": "javascript",
"description": "Define a parameter in a GDevelop extension definition.",
"prefix": "gdcoparam",
"body": [
".addCodeOnlyParameter('${1|inlineCode,currentScene,objectsContext,eventsFunctionContext,conditionInverted|}', '${2:Inline code (for inlineCode parameter)}')"
]
},
"Add include": {
"scope": "javascript",
"description": "Define an include file in a GDevelop extension definition.",
"prefix": "gdincl",
"body": [
".addIncludeFile('Extensions/${TM_DIRECTORY/(.*)[\\\\\\/](.*)[\\\\\\/]?$/$2/}/${1:include}.js')"
]
}
}

View File

@@ -1,107 +1,98 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
],
"defines": [
"GD_IDE_ONLY",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
},
{
"name": "Linux",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
],
"defines": [
"GD_IDE_ONLY",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
},
{
"name": "Win32",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/IDE",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include",
"${workspaceRoot}"
],
"defines": [
"_DEBUG",
"UNICODE",
"GD_IDE_ONLY",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": [
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
}
],
"version": 4
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
],
"defines": [
"EMSCRIPTEN",
"GD_IDE_ONLY",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"macFrameworkPath": ["/System/Library/Frameworks", "/Library/Frameworks"],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
},
{
"name": "Linux",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
],
"defines": [
"EMSCRIPTEN",
"GD_IDE_ONLY",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": ["/usr/include", "/usr/local/include", "${workspaceRoot}"],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
},
{
"name": "Win32",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"${workspaceRoot}"
],
"defines": [
"_DEBUG",
"UNICODE",
"EMSCRIPTEN",
"GD_IDE_ONLY",
"GD_CORE_API=/* Macro used to export classes on Windows, please ignore */",
"GD_API=/* Macro used to export classes on Windows, please ignore */",
"GD_EXTENSION_API=/* Macro used to export classes on Windows, please ignore */"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": ["${workspaceRoot}"],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
}
],
"version": 4
}

View File

@@ -1,8 +1,8 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"xaver.clang-format",
"ms-vscode.cpptools",
"flowtype.flow-for-vscode"
]
}
"recommendations": [
"esbenp.prettier-vscode",
"xaver.clang-format",
"ms-vscode.cpptools",
"flowtype.flow-for-vscode"
]
}

29
.vscode/launch.json vendored
View File

@@ -1,15 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"preLaunchTask": "Start development server"
}
]
}

View File

@@ -106,12 +106,15 @@
"xstring": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
"xutility": "cpp",
"xlocbuf": "cpp",
"xlocmes": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,
"Binaries/Output": true,
"ExtLibs/SFML": true,
"GDJS/Runtime-dist": true,
"docs": true,
"newIDE/electron-app/dist": true,
"newIDE/app/build": true,

158
.vscode/tasks.json vendored
View File

@@ -1,66 +1,100 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"path": "newIDE/app/",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"label": "Start development server",
"detail": "Starts the GDevelop development server."
},
{
"type": "npm",
"script": "build",
"path": "GDevelop.js/",
"group": "build",
"problemMatcher": [],
"label": "Build GDevelop.js",
"detail": "Builds GDCore for newIDE."
},
{
"type": "npm",
"script": "format",
"path": "newIDE/app/",
"problemMatcher": [],
"label": "Format newIDE",
"detail": "Run auto-formatting (with Prettier) for the newIDE/app directory."
},
{
"type": "npm",
"script": "test",
"path": "newIDE/app/",
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": [],
"label": "Run newIDE tests",
"detail": "Run tests for newIDE."
},
{
"type": "typescript",
"tsconfig": "GDJS/tsconfig.json",
"option": "watch",
"problemMatcher": [
"$tsc-watch"
],
"group": "test",
"label": "GDJS TS Check",
"detail": "Runs a types check on the GDJS Runtime."
},
{
"type": "npm",
"script": "test",
"path": "GDJS/",
"group": "test",
"problemMatcher": [],
"label": "Run GDJS tests",
"detail": "Run tests for GDJS."
}
]
{
"type": "npm",
"script": "start",
"path": "newIDE/app/",
"group": "build",
"label": "Start development server",
"detail": "Starts the GDevelop development server.",
"problemMatcher": [
{
"owner": "cra",
"fileLocation": ["relative", "${workspaceFolder}/newIDE/app"],
"source": "create-react-app",
"applyTo": "allDocuments",
"pattern": [
{
"regexp": "^([^\\s].*?)$",
"file": 1
},
{
"regexp": "^ Line\\s+(\\d+):\\s+(.*)\\s\\s+(.*)$",
"line": 1,
"message": 2,
"code": 3,
"loop": true
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "^(?:Compiled with warnings\\.|Compiled successfully!)$",
"endsPattern": "^(?:Search for the keywords to learn more about each warning\\.|Note that the development build is not optimized\\.)$"
}
}
],
"presentation": {
"reveal": "silent"
},
"isBackground": true,
"runOptions": { "instanceLimit": 1, "runOn": "folderOpen" }
},
{
"type": "npm",
"script": "start",
"path": "newIDE/electron-app/",
"group": "build",
"problemMatcher": [],
"label": "Start electron app",
"detail": "Starts the development local version of GDevelop."
},
{
"type": "npm",
"script": "build",
"path": "GDevelop.js/",
"group": "build",
"problemMatcher": [],
"label": "Build GDevelop.js",
"detail": "Builds GDCore for newIDE."
},
{
"type": "npm",
"script": "format",
"path": "newIDE/app/",
"problemMatcher": [],
"label": "Format newIDE",
"detail": "Run auto-formatting (with Prettier) for the newIDE/app directory."
},
{
"type": "npm",
"script": "test",
"path": "newIDE/app/",
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": [],
"label": "Run newIDE tests",
"detail": "Run tests for newIDE."
},
{
"type": "typescript",
"tsconfig": "GDJS/tsconfig.json",
"option": "watch",
"problemMatcher": ["$tsc-watch"],
"group": "test",
"label": "GDJS TS Check",
"detail": "Runs a types check on the GDJS Runtime."
},
{
"type": "npm",
"script": "test",
"path": "GDJS/",
"group": "test",
"problemMatcher": [],
"label": "Run GDJS tests",
"detail": "Run tests for GDJS."
}
]
}

View File

@@ -9,6 +9,7 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Project/Effect.h"
#include "GDCore/Project/Layer.h"
#include "GDCore/Project/EffectsContainer.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
@@ -26,10 +27,9 @@ void ExposeProjectEffects(
auto& layout = project.GetLayout(s);
for (std::size_t l = 0; l < layout.GetLayersCount(); ++l) {
auto& layer = layout.GetLayer(l);
for (std::size_t e = 0; e < layer.GetEffectsCount(); ++e) {
auto& effect = layer.GetEffect(e);
auto& effects = layout.GetLayer(l).GetEffects();
for (std::size_t e = 0; e < effects.GetEffectsCount(); ++e) {
auto& effect = effects.GetEffect(e);
worker(effect);
}
}

View File

@@ -360,8 +360,11 @@ gd::String ExpressionCodeGenerator::GenerateDefaultValue(
if (gd::ParameterMetadata::IsObject(type)) {
return codeGenerator.GenerateBadObject();
}
if (gd::ParameterMetadata::IsExpression("string", type)) {
return "\"\"";
}
return (type == "string") ? "\"\"" : "0";
return "0";
}
void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {

View File

@@ -66,6 +66,22 @@ class GD_CORE_API ExpressionParser2 {
return Start(type, objectName);
}
/**
* Given an object name (or empty if none) and a behavior name (or empty if none),
* return the index of the first parameter that is inside the parenthesis:
* 0, 1 or 2.
*
* For example, in an expression like `Object.MyBehavior::Method("hello")`, the
* parameter "hello" is the second parameter (the first being by convention Object,
* and the second MyBehavior, also by convention).
*/
static size_t WrittenParametersFirstIndex(const gd::String &objectName,
const gd::String &behaviorName) {
// By convention, object is always the first parameter, and behavior the
// second one.
return !behaviorName.empty() ? 2 : (!objectName.empty() ? 1 : 0);
}
private:
/** \name Grammar
* Each method is a part of the grammar.
@@ -999,13 +1015,6 @@ class GD_CORE_API ExpressionParser2 {
}
///@}
static size_t WrittenParametersFirstIndex(const gd::String &objectName,
const gd::String &behaviorName) {
// By convention, object is always the first parameter, and behavior the
// second one.
return !behaviorName.empty() ? 2 : (!objectName.empty() ? 1 : 0);
}
gd::String expression;
std::size_t currentPosition;

View File

@@ -206,7 +206,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"all of the forces it has."),
_("Add to _PARAM0_ _PARAM3_ force of _PARAM1_ p/s on X axis and "
"_PARAM2_ p/s on Y axis"),
_("Movement"),
_("Movement using forces"),
"res/actions/force24.png",
"res/actions/force.png")
@@ -222,7 +222,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"using the specified angle and length."),
_("Add to _PARAM0_ _PARAM3_ force, angle: _PARAM1_ degrees and "
"length: _PARAM2_ pixels"),
_("Movement"),
_("Movement using forces"),
"res/actions/force24.png",
"res/actions/force.png")
@@ -238,7 +238,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Add a force to an object to make it move toward a position."),
_("Move _PARAM0_ toward _PARAM1_;_PARAM2_ with _PARAM4_ force of _PARAM3_ "
"pixels"),
_("Movement"),
_("Movement using forces"),
"res/actions/force24.png",
"res/actions/force.png")
@@ -251,30 +251,30 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddAction(
"AddForceTournePos",
_("Add a force to move around a position"),
_("Add a force to an object to make it rotate around a "
"Add a force to move around a position",
"Add a force to an object to make it rotate around a "
"position.\nNote that the movement is not precise, especially if "
"the speed is high.\nTo position an object around a position more "
"precisely, use the actions in the category \"Position\"."),
_("Rotate _PARAM0_ around _PARAM1_;_PARAM2_ at _PARAM3_ deg/sec and "
"_PARAM4_ pixels away"),
_("Movement"),
"precisely, use the actions in the category \"Position\".",
"Rotate _PARAM0_ around _PARAM1_;_PARAM2_ at _PARAM3_ deg/sec and "
"_PARAM4_ pixels away",
_("Movement using forces"),
"res/actions/forceTourne24.png",
"res/actions/forceTourne.png")
.AddParameter("object", _("Object"))
.AddParameter("expression", _("X position of the center"))
.AddParameter("expression", _("Y position of the center"))
.AddParameter("expression", _("Speed (in Degrees per seconds)"))
.AddParameter("expression", _("Distance (in pixels)"))
.AddParameter("forceMultiplier", _("Force multiplier"))
.AddParameter("expression", "X position of the center")
.AddParameter("expression", "Y position of the center")
.AddParameter("expression", "Speed (in Degrees per seconds)")
.AddParameter("expression", "Distance (in pixels)")
.AddParameter("forceMultiplier", "Force multiplier")
.SetHidden();
obj.AddAction("Arreter",
_("Stop the object"),
_("Stop the object by deleting all of its forces."),
_("Stop _PARAM0_ (remove all forces)"),
_("Movement"),
_("Movement using forces"),
"res/actions/arreter24.png",
"res/actions/arreter.png")
@@ -475,9 +475,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsSimple();
obj.AddCondition("Invisible",
_("Invisibility of an object"),
_("Check if an object is hidden."),
_("_PARAM0_ is hidden"),
"Invisibility of an object",
"Check if an object is hidden.",
"_PARAM0_ is hidden",
_("Visibility"),
"res/conditions/visibilite24.png",
"res/conditions/visibilite.png")
@@ -486,10 +486,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.SetHidden(); // Inverted "Visible" condition does the same thing.
obj.AddCondition("Arret",
_("Object is stopped"),
_("Object is stopped (no forces applied on it)"),
_("Check if an object is not moving"),
_("_PARAM0_ is stopped"),
_("Movement"),
_("Movement using forces"),
"res/conditions/arret24.png",
"res/conditions/arret.png")
@@ -497,10 +497,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced();
obj.AddCondition("Vitesse",
_("Speed"),
_("Speed (from forces)"),
_("Compare the overall speed of an object"),
_("the overall speed"),
_("Movement"),
_("Movement using forces"),
"res/conditions/vitesse24.png",
"res/conditions/vitesse.png")
@@ -509,17 +509,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced();
obj.AddCondition("AngleOfDisplacement",
_("Angle of movement"),
_("Compare the angle of displacement of an object"),
_("Angle of displacement of _PARAM0_ is _PARAM1_ (tolerance "
_("Angle of movement (using forces)"),
_("Compare the angle of movement of an object according to the forces applied on it."),
_("Angle of movement of _PARAM0_ is _PARAM1_ (tolerance"
": _PARAM2_ degrees)"),
_("Movement"),
_("Movement using forces"),
"res/conditions/vitesse24.png",
"res/conditions/vitesse.png")
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Tolerance"))
.AddParameter("expression", _("Tolerance, in degrees"))
.MarkAsAdvanced();
obj.AddCondition("VarObjet",
@@ -560,9 +560,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.SetDefaultValue("true");
obj.AddCondition("VarObjetDef",
_("Variable defined"),
_("Check if the variable is defined."),
_("Variable _PARAM1 of _PARAM0_ is defined"),
"Variable defined",
"Check if the variable is defined.",
"Variable _PARAM1 of _PARAM0_ is defined",
_("Variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
@@ -632,7 +632,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Removes a variable at the specified index of an object array "
"variable."),
_("Remove variable at index _PARAM2_ from array variable _PARAM1_ of "
"object _PARAM0_"),
"_PARAM0_"),
_("Variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
@@ -671,7 +671,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Add a force to move toward an object"),
_("Add a force to an object to make it move toward another."),
_("Move _PARAM0_ toward _PARAM1_ with _PARAM3_ force of _PARAM2_ pixels"),
_("Movement"),
_("Movement using forces"),
"res/actions/forceVers24.png",
"res/actions/forceVers.png")
@@ -690,7 +690,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"the actions in category \"Position\"."),
_("Rotate _PARAM0_ around _PARAM1_ at _PARAM2_ deg/sec and _PARAM3_ "
"pixels away"),
_("Movement"),
_("Movement using forces"),
"res/actions/forceTourne24.png",
"res/actions/forceTourne.png")
@@ -720,29 +720,29 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
// Deprecated action
obj.AddAction("Rebondir",
_("Move an object away from another"),
_("Move an object away from another, using forces."),
_("Move _PARAM0_ away from _PARAM1_ (only _PARAM0_ will move)"),
_("Movement"),
"Move an object away from another",
"Move an object away from another, using forces.",
"Move _PARAM0_ away from _PARAM1_ (only _PARAM0_ will move)",
_("Movement using forces"),
"res/actions/ecarter24.png",
"res/actions/ecarter.png")
.SetHidden()
.AddParameter("object", _("Object"))
.AddParameter("objectList", _("Object 2 (won't move)"));
.AddParameter("objectList", "Object 2 (won't move)");
// Deprecated action
obj.AddAction("Ecarter",
_("Move an object away from another"),
_("Move an object away from another without using forces."),
_("Move _PARAM0_ away from _PARAM2_ (only _PARAM0_ will move)"),
"Move an object away from another",
"Move an object away from another without using forces.",
"Move _PARAM0_ away from _PARAM2_ (only _PARAM0_ will move)",
_("Position"),
"res/actions/ecarter24.png",
"res/actions/ecarter.png")
.SetHidden()
.AddParameter("object", _("Object"))
.AddParameter("objectList", _("Object 2 (won't move)"));
.AddParameter("objectList", "Object 2 (won't move)");
obj.AddAction("SeparateFromObjects",
_("Separate objects"),
@@ -882,37 +882,37 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("object", _("Object"));
obj.AddExpression("ForceX",
_("Average X coordinates of forces"),
_("Average X coordinates of forces"),
_("Movement"),
_("X coordinate of the sum of forces"),
_("X coordinate of the sum of forces"),
_("Movement using forces"),
"res/actions/force.png")
.AddParameter("object", _("Object"));
obj.AddExpression("ForceY",
_("Average Y coordinates of forces"),
_("Average Y coordinates of forces"),
_("Movement"),
_("Y coordinate of the sum of forces"),
_("Y coordinate of the sum of forces"),
_("Movement using forces"),
"res/actions/force.png")
.AddParameter("object", _("Object"));
obj.AddExpression("ForceAngle",
_("Average angle of the forces"),
_("Average angle of the forces"),
_("Movement"),
_("Angle of the sum of forces"),
_("Angle of the sum of forces"),
_("Movement using forces"),
"res/actions/force.png")
.AddParameter("object", _("Object"));
obj.AddExpression("ForceLength",
_("Average length of the forces"),
_("Average length of the forces"),
_("Movement"),
_("Length of the sum of forces"),
_("Length of the sum of forces"),
_("Movement using forces"),
"res/actions/force.png")
.AddParameter("object", _("Object"));
obj.AddExpression("Longueur",
_("Average length of the forces"),
_("Average length of the forces"),
_("Movement"),
_("Length of the sum of forces"),
_("Length of the sum of forces"),
_("Movement using forces"),
"res/actions/force.png")
.AddParameter("object", _("Object"))
.SetHidden();
@@ -1149,7 +1149,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Moves all objects according to the forces they have. GDevelop "
"calls this action at the end of the events by default."),
_("Apply movement to all objects"),
_("Movement"),
_("Movement using forces"),
"res/actions/doMove24.png",
"res/actions/doMove.png")
.AddCodeOnlyParameter("currentScene", "")
@@ -1157,16 +1157,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
extension
.AddCondition("SeDirige",
_("An object is moving toward another"),
_("An object is moving toward another (using forces)"),
_("Check if an object moves toward another.\nThe first "
"object must move."),
_("_PARAM0_ is moving toward _PARAM1_"),
_("Movement"),
_("Movement using forces"),
"res/conditions/sedirige24.png",
"res/conditions/sedirige.png")
.AddParameter("objectList", _("Object"))
.AddParameter("objectList", _("Object 2"))
.AddParameter("expression", _("Angle of tolerance"))
.AddParameter("expression", _("Tolerance, in degrees"))
.AddCodeOnlyParameter("conditionInverted", "")
.MarkAsAdvanced();

View File

@@ -15,7 +15,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
.SetExtensionInformation(
"BuiltinNetwork",
_("Basic internet features"),
_("Features to send web requests, communicate with external \"APIs\" and other network related tasks."),
_("Features to send web requests, communicate with external \"APIs\" "
"and other network related tasks."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/network");
@@ -24,33 +25,33 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
extension
.AddAction(
"SendRequest",
_("Send a request to a web page"),
_("Send a request to the specified web page.\n\nPlease note that for "
"the web games, the game must be hosted on the same host "
"as specified below, except if the server is configured to answer "
"to all requests (cross-domain requests)."),
_("Send _PARAM3_ request to _PARAM0__PARAM1_ with body: _PARAM2_"),
"Send a request to a web page",
"Send a request to the specified web page.\n\nPlease note that for "
"the web games, the game must be hosted on the same host "
"as specified below, except if the server is configured to answer "
"to all requests (cross-domain requests).",
"Send _PARAM3_ request to _PARAM0__PARAM1_ with body: _PARAM2_",
_("Network"),
"res/actions/net24.png",
"res/actions/net.png")
.AddParameter("string", _("Host, with protocol"))
.SetParameterLongDescription(_("Example: \"http://example.com/\"."))
.AddParameter("string", _("Path"))
.AddParameter("string", "Host, with protocol")
.SetParameterLongDescription("Example: \"http://example.com/\".")
.AddParameter("string", "Path")
.SetParameterLongDescription(
_("Example: \"/user/123\" or \"/some-page.php\"."))
.AddParameter("string", _("Request body content"))
.AddParameter("string", _("Method: \"POST\" or \"GET\""), "", true)
.SetParameterLongDescription(_("If empty, \"GET\" will be used."))
"Example: \"/user/123\" or \"/some-page.php\".")
.AddParameter("string", "Request body content")
.AddParameter("string", "Method: \"POST\" or \"GET\"", "", true)
.SetParameterLongDescription("If empty, \"GET\" will be used.")
.SetDefaultValue("\"GET\"")
.AddParameter("string", _("Content type"), "", true)
.AddParameter("string", "Content type", "", true)
.SetParameterLongDescription(
_("If empty, \"application/x-www-form-urlencoded\" will be used."))
.AddParameter("scenevar", _("Reponse scene variable"), "", true)
"If empty, \"application/x-www-form-urlencoded\" will be used.")
.AddParameter("scenevar", "Reponse scene variable", "", true)
.SetParameterLongDescription(
_("The response of the server will be stored, as a string, in this "
"variable. If the server returns *JSON*, you may want to use the "
"action \"Convert JSON to a scene variable\" afterwards, to "
"explore the results with a *structure variable*."))
"The response of the server will be stored, as a string, in this "
"variable. If the server returns *JSON*, you may want to use the "
"action \"Convert JSON to a scene variable\" afterwards, to "
"explore the results with a *structure variable*.")
.MarkAsComplex()
.SetHidden();
@@ -74,7 +75,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
"highly recommended."))
.AddParameter("string", _("Request body content"))
.AddParameter("stringWithSelector",
_("Resize mode"),
_("Request method"),
"[\"GET\", \"POST\", \"PUT\", \"HEAD\", \"DELETE\", "
"\"PATCH\", \"OPTIONS\"]",
false)

View File

@@ -66,7 +66,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/animation.png")
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("string", _("Animation name"))
.AddParameter("objectAnimationName", _("Animation name"))
.MarkAsAdvanced();
obj.AddAction(
@@ -133,9 +133,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.MarkAsSimple();
obj.AddAction("TourneVersPos",
_("Rotate an object toward a position"),
_("Rotate an object towards a position."),
_("Rotate _PARAM0_ towards _PARAM1_;_PARAM2_"),
"Rotate an object toward a position",
"Rotate an object towards a position.",
"Rotate _PARAM0_ towards _PARAM1_;_PARAM2_",
_("Direction"),
"res/actions/direction24.png",
"res/actions/direction.png")
@@ -230,7 +230,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/conditions/animation.png")
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("string", _("Animation name"))
.AddParameter("objectAnimationName", _("Animation name"))
.MarkAsAdvanced();
obj.AddCondition(
@@ -249,7 +249,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
obj.AddCondition("Sprite",
_("Current frame"),
_("Compare the index of the current frame in the animation displayed by the specified object. The first frame in an animation starts at index 0."),
_("Compare the index of the current frame in the animation "
"displayed by the specified object. The first frame in an "
"animation starts at index 0."),
_("the animation frame"),
_("Animations and images"),
"res/conditions/sprite24.png",
@@ -365,14 +367,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("color", _("Color to make transparent"));
obj.AddAction(
"ChangeColor",
_("Tint color"),
_("Change the tint of an object. The default color is white."),
_("Change tint of _PARAM0_ to _PARAM1_"),
_("Effects"),
"res/actions/color24.png",
"res/actions/color.png")
obj.AddAction("ChangeColor",
_("Tint color"),
_("Change the tint of an object. The default color is white."),
_("Change tint of _PARAM0_ to _PARAM1_"),
_("Effects"),
"res/actions/color24.png",
"res/actions/color.png")
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("color", _("Tint"));
@@ -436,15 +437,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddParameter("object", _("Object"), "Sprite");
obj.AddAction("TourneVers",
_("Rotate an object toward another"),
_("Rotate an object towards another."),
_("Rotate _PARAM0_ towards _PARAM1_"),
"Rotate an object toward another",
"Rotate an object towards another.",
"Rotate _PARAM0_ towards _PARAM1_",
_("Direction"),
"res/actions/direction24.png",
"res/actions/direction.png")
.AddParameter("object", _("Object to be rotated"), "Sprite")
.AddParameter("objectPtr", _("Rotate toward this object"))
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("objectPtr", "Rotate toward this object")
.AddCodeOnlyParameter("currentScene", "")
.SetHidden(); // Deprecated
@@ -455,7 +456,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/position.png")
.SetHidden()
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("string", _("Name of the point"), "", true);
.AddParameter("objectPointName", _("Name of the point"), "", true);
obj.AddExpression("Y",
_("Y position of a point"),
@@ -464,7 +465,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/position.png")
.SetHidden()
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("string", _("Name of the point"), "", true);
.AddParameter("objectPointName", _("Name of the point"), "", true);
obj.AddExpression("PointX",
_("X position of a point"),
@@ -473,7 +474,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/position.png")
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("string", _("Name of the point"));
.AddParameter("objectPointName", _("Name of the point"));
obj.AddExpression("PointY",
_("Y position of a point"),
@@ -482,7 +483,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/position.png")
.AddParameter("object", _("Object"), "Sprite")
.AddParameter("string", _("Name of the point"));
.AddParameter("objectPointName", _("Name of the point"));
obj.AddExpression("Direc",
_("Direction"),

View File

@@ -112,9 +112,9 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
extension
.AddExpression("StrRFind",
_("Search in a text from the end"),
_("Search in a text from the end (return the position of "
"the result or -1 if not found)"),
"Search in a text from the end",
"Search in a text from the end (return the position of "
"the result or -1 if not found)",
_("Manipulation of text"),
"res/conditions/toujours24.png")
@@ -152,17 +152,17 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
extension
.AddExpression(
"StrRFindFrom",
_("Search in a text from the end, starting from a position"),
_("Search in a text from the end, starting from a position (return "
"the position of the result or -1 if not found)"),
"Search in a text from the end, starting from a position",
"Search in a text from the end, starting from a position (return "
"the position of the result or -1 if not found)",
_("Manipulation of text"),
"res/conditions/toujours24.png")
.AddParameter("string", _("Text"))
.AddParameter("string", _("Text to search for"))
.AddParameter("expression",
_("Position of the last character in the string to be "
"considered in the search"))
"Position of the last character in the string to be "
"considered in the search")
.SetHidden(); // Deprecated, see StrFindLastFrom instead.
extension

View File

@@ -87,9 +87,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarSceneDef",
_("Test if a scene variable is defined"),
_("Test if the scene variable exists."),
_("Scene variable _PARAM0_ is defined"),
"Test if a scene variable is defined",
"Test if the scene variable exists.",
"Scene variable _PARAM0_ is defined",
_("Variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
@@ -136,9 +136,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
extension
.AddCondition("VarGlobalDef",
_("Test if a global variable is defined"),
_("Test if a global variable exists"),
_("Global variable _PARAM0_ is defined"),
"Test if a global variable is defined",
"Test if a global variable exists",
"Global variable _PARAM0_ is defined",
_("Variables/Global variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
@@ -341,7 +341,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
"SceneVariableRemoveAt",
_("Remove variable from a scene array (by index)"),
_("Removes a variable at the specified index of a scene array variable."),
_("Remove variable at index _PARAM1_ from array variable _PARAM0_"),
_("Remove variable at index _PARAM1_ from scene array variable _PARAM0_"),
_("Variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
@@ -367,7 +367,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
"GlobalVariableRemoveAt",
_("Remove variable from a global array (by index)"),
_("Removes a variable at the specified index of a global array variable."),
_("Remove variable at index _PARAM1_ from array variable _PARAM0_"),
_("Remove variable at index _PARAM1_ from global array variable _PARAM0_"),
_("Variables/Global variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")

View File

@@ -19,7 +19,7 @@ using namespace std;
namespace gd {
gd::BehaviorMetadata MetadataProvider::badBehaviorInfo;
gd::BehaviorMetadata MetadataProvider::badBehaviorMetadata;
gd::ObjectMetadata MetadataProvider::badObjectInfo;
gd::EffectMetadata MetadataProvider::badEffectMetadata;
gd::InstructionMetadata MetadataProvider::badInstructionMetadata;
@@ -38,7 +38,7 @@ MetadataProvider::GetExtensionAndBehaviorMetadata(const gd::Platform& platform,
}
}
return ExtensionAndMetadata<BehaviorMetadata>(badExtension, badBehaviorInfo);
return ExtensionAndMetadata<BehaviorMetadata>(badExtension, badBehaviorMetadata);
}
const BehaviorMetadata& MetadataProvider::GetBehaviorMetadata(

View File

@@ -245,13 +245,17 @@ class GD_CORE_API MetadataProvider {
return &metadata == &badExpressionMetadata;
}
static bool IsBadBehaviorMetadata(const gd::BehaviorMetadata& metadata) {
return &metadata == &badBehaviorMetadata;
}
virtual ~MetadataProvider();
private:
MetadataProvider();
static PlatformExtension badExtension;
static BehaviorMetadata badBehaviorInfo;
static BehaviorMetadata badBehaviorMetadata;
static ObjectMetadata badObjectInfo;
static EffectMetadata badEffectMetadata;
static gd::InstructionMetadata badInstructionMetadata;

View File

@@ -175,7 +175,8 @@ class GD_CORE_API ParameterMetadata {
* \brief Return true if the type of the parameter is an expression of the
* given type.
* \note If you had a new type of parameter, also add it in the IDE (
* see EventsFunctionParametersEditor) and in the EventsCodeGenerator.
* see EventsFunctionParametersEditor, ParameterRenderingService
* and ExpressionAutocompletion) and in the EventsCodeGenerator.
*/
static bool IsExpression(const gd::String &type,
const gd::String &parameterType) {
@@ -187,7 +188,9 @@ class GD_CORE_API ParameterMetadata {
parameterType == "color" || parameterType == "file" ||
parameterType == "joyaxis" ||
parameterType == "stringWithSelector" ||
parameterType == "sceneName";
parameterType == "sceneName" ||
parameterType == "objectPointName" ||
parameterType == "objectAnimationName";
} else if (type == "variable") {
return parameterType == "objectvar" || parameterType == "globalvar" ||
parameterType == "scenevar";

View File

@@ -100,26 +100,6 @@ std::unique_ptr<gd::Object> Platform::CreateObject(
return std::unique_ptr<gd::Object>(std::move(object));
}
gd::Behavior* Platform::GetBehavior(const gd::String& behaviorType) const {
for (std::size_t i = 0; i < extensionsLoaded.size(); ++i) {
gd::Behavior* behavior = extensionsLoaded[i]->GetBehavior(behaviorType);
if (behavior) return behavior;
}
return nullptr;
}
gd::BehaviorsSharedData* Platform::GetBehaviorSharedDatas(
const gd::String& behaviorType) const {
for (std::size_t i = 0; i < extensionsLoaded.size(); ++i) {
gd::BehaviorsSharedData* behaviorSharedData =
extensionsLoaded[i]->GetBehaviorSharedDatas(behaviorType);
if (behaviorSharedData) return behaviorSharedData;
}
return nullptr;
}
#if defined(GD_IDE_ONLY)
std::shared_ptr<gd::BaseEvent> Platform::CreateEvent(
const gd::String& eventType) const {

View File

@@ -126,7 +126,8 @@ class GD_CORE_API Platform {
///@}
/** \name Factory method
* Member functions used to create the platforms objects
* Member functions used to create the platform objects.
* TODO: This could be moved to gd::MetadataProvider.
*/
///@{
@@ -136,18 +137,6 @@ class GD_CORE_API Platform {
std::unique_ptr<gd::Object> CreateObject(gd::String type,
const gd::String& name) const;
/**
* \brief Get the class handling the behavior with the given type, or
* `nullptr` if no behavior with the given type is found.
*/
gd::Behavior* GetBehavior(const gd::String& type) const;
/**
* \brief Get the class handling the behavior shared data with the given type,
* or `nullptr` if no behavior with the given type is found.
*/
gd::BehaviorsSharedData* GetBehaviorSharedDatas(const gd::String& type) const;
#if defined(GD_IDE_ONLY)
/**
* \brief Create an event of given type
@@ -165,11 +154,13 @@ class GD_CORE_API Platform {
/**
* \brief Called when the IDE is about to shut down: Take this opportunity for
* erasing for example any temporary file.
* @deprecated This should be removed.
*/
virtual void OnIDEClosed(){};
/**
* \brief Called when the IDE is initialized and ready to be used.
* @deprecated This should be removed.
*/
virtual void OnIDEInitialized(){};

View File

@@ -512,7 +512,7 @@ PlatformExtension::GetAllStrExpressionsForBehavior(gd::String autoType) {
return badExpressionsMetadata;
}
gd::BaseEventSPtr PlatformExtension::CreateEvent(gd::String eventType) const {
gd::BaseEventSPtr PlatformExtension::CreateEvent(const gd::String& eventType) const {
if (eventsInfos.find(eventType) != eventsInfos.end()) {
if (eventsInfos.find(eventType)->second.instance ==
std::shared_ptr<BaseEvent>()) {
@@ -531,14 +531,14 @@ gd::BaseEventSPtr PlatformExtension::CreateEvent(gd::String eventType) const {
#endif
CreateFunPtr PlatformExtension::GetObjectCreationFunctionPtr(
gd::String objectType) const {
const gd::String& objectType) const {
if (objectsInfos.find(objectType) != objectsInfos.end())
return objectsInfos.find(objectType)->second.createFunPtr;
return NULL;
}
gd::Behavior* PlatformExtension::GetBehavior(gd::String type) const {
gd::Behavior* PlatformExtension::GetBehavior(const gd::String& type) const {
if (behaviorsInfo.find(type) != behaviorsInfo.end())
return &behaviorsInfo.find(type)->second.Get();
@@ -546,7 +546,7 @@ gd::Behavior* PlatformExtension::GetBehavior(gd::String type) const {
}
gd::BehaviorsSharedData* PlatformExtension::GetBehaviorSharedDatas(
gd::String type) const {
const gd::String& type) const {
if (behaviorsInfo.find(type) != behaviorsInfo.end() &&
behaviorsInfo.find(type)->second.GetSharedDataInstance())
return behaviorsInfo.find(type)->second.GetSharedDataInstance();

View File

@@ -400,7 +400,7 @@ class GD_CORE_API PlatformExtension {
* \brief Return a function to create the object if the type is handled by the
* extension
*/
CreateFunPtr GetObjectCreationFunctionPtr(gd::String objectType) const;
CreateFunPtr GetObjectCreationFunctionPtr(const gd::String& objectType) const;
/**
* \brief Return a vector containing all the effect types provided by the
@@ -413,13 +413,13 @@ class GD_CORE_API PlatformExtension {
*
* Return an empty pointer if \a eventType is not provided by the extension.
*/
std::shared_ptr<gd::BaseEvent> CreateEvent(gd::String eventType) const;
std::shared_ptr<gd::BaseEvent> CreateEvent(const gd::String& eventType) const;
/**
* \brief Get the gd::Behavior handling the given behavior type.
*
* Return nullptr if \a behaviorType is not provided by the extension.
*/
gd::Behavior* GetBehavior(gd::String behaviorType) const;
gd::Behavior* GetBehavior(const gd::String& behaviorType) const;
/**
* \brief Get the gd::BehaviorsSharedData handling the given behavior shared
@@ -428,7 +428,7 @@ class GD_CORE_API PlatformExtension {
* Return nullptr if \a behaviorType is not provided by the extension.
*/
gd::BehaviorsSharedData* GetBehaviorSharedDatas(
gd::String behaviorType) const;
const gd::String& behaviorType) const;
/**
* \brief Return a reference to the ObjectMetadata object associated to \a

View File

@@ -15,25 +15,18 @@
#include "GDCore/Project/Project.h"
#include "GDCore/Project/SourceFile.h"
DependenciesAnalyzer::DependenciesAnalyzer(gd::Project& project_,
gd::Layout& layout_)
DependenciesAnalyzer::DependenciesAnalyzer(const gd::Project& project_,
const gd::Layout& layout_)
: project(project_), layout(&layout_), externalEvents(NULL) {
parentScenes.push_back(layout->GetName());
}
DependenciesAnalyzer::DependenciesAnalyzer(gd::Project& project_,
gd::ExternalEvents& externalEvents_)
DependenciesAnalyzer::DependenciesAnalyzer(const gd::Project& project_,
const gd::ExternalEvents& externalEvents_)
: project(project_), layout(NULL), externalEvents(&externalEvents_) {
parentExternalEvents.push_back(externalEvents->GetName());
}
DependenciesAnalyzer::DependenciesAnalyzer(const DependenciesAnalyzer& parent)
: parentScenes(parent.parentScenes),
parentExternalEvents(parent.parentExternalEvents),
project(parent.project),
layout(NULL),
externalEvents(NULL) {}
bool DependenciesAnalyzer::Analyze() {
if (layout)
return Analyze(layout->GetEvents(), true);
@@ -47,9 +40,9 @@ bool DependenciesAnalyzer::Analyze() {
DependenciesAnalyzer::~DependenciesAnalyzer() {}
bool DependenciesAnalyzer::Analyze(gd::EventsList& events, bool isOnTopLevel) {
bool DependenciesAnalyzer::Analyze(const gd::EventsList& events, bool isOnTopLevel) {
for (unsigned int i = 0; i < events.size(); ++i) {
gd::LinkEvent* linkEvent = dynamic_cast<gd::LinkEvent*>(&events[i]);
const gd::LinkEvent* linkEvent = dynamic_cast<const gd::LinkEvent*>(&events[i]);
if (linkEvent) {
DependenciesAnalyzer analyzer(*this);
@@ -113,7 +106,7 @@ bool DependenciesAnalyzer::Analyze(gd::EventsList& events, bool isOnTopLevel) {
sourceFilesDependencies.insert(dependencies.begin(), dependencies.end());
const gd::String& associatedSourceFile =
events[i].GetAssociatedGDManagedSourceFile(project);
events[i].GetAssociatedGDManagedSourceFile(const_cast<gd::Project&>(project));
if (!associatedSourceFile.empty())
sourceFilesDependencies.insert(associatedSourceFile);

View File

@@ -35,7 +35,7 @@ class GD_CORE_API DependenciesAnalyzer {
/**
* \brief Constructor for analyzing the dependencies of a layout
*/
DependenciesAnalyzer(gd::Project& project_, gd::Layout& layout_);
DependenciesAnalyzer(const gd::Project& project_, const gd::Layout& layout_);
/**
* \brief Constructor for analyzing the dependencies of external events.
@@ -45,8 +45,8 @@ class GD_CORE_API DependenciesAnalyzer {
* external events can be compiled separatly and called by a scene. \see
* DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene
*/
DependenciesAnalyzer(gd::Project& project_,
gd::ExternalEvents& externalEvents);
DependenciesAnalyzer(const gd::Project& project_,
const gd::ExternalEvents& externalEvents);
virtual ~DependenciesAnalyzer();
@@ -124,13 +124,7 @@ class GD_CORE_API DependenciesAnalyzer {
* (they have no parents). \return false if a circular dependency exists, true
* otherwise.
*/
bool Analyze(gd::EventsList& events, bool isOnTopLevel);
/**
* \brief Internal constructor used when analyzing a linked layout/external
* events.
*/
DependenciesAnalyzer(const DependenciesAnalyzer& parent);
bool Analyze(const gd::EventsList& events, bool isOnTopLevel);
void AddParentScene(gd::String parentScene) {
parentScenes.push_back(parentScene);
@@ -161,9 +155,9 @@ class GD_CORE_API DependenciesAnalyzer {
std::vector<gd::String>
parentExternalEvents; ///< Used to check for circular dependencies.
gd::Project& project;
gd::Layout* layout;
gd::ExternalEvents* externalEvents;
const gd::Project& project;
const gd::Layout* layout;
const gd::ExternalEvents* externalEvents;
};
#endif // DEPENDENCIESANALYZER_H

View File

@@ -17,6 +17,8 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/IDE/DependenciesAnalyzer.h"
using namespace std;
@@ -95,11 +97,11 @@ std::set<gd::String> EventsVariablesFinder::FindAllGlobalVariables(
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
std::set<gd::String> results2 =
FindArgumentsInEvents(platform,
project,
project.GetLayout(i),
project.GetLayout(i).GetEvents(),
"globalvar");
FindArgumentsInEventsAndDependencies(
platform,
project,
project.GetLayout(i),
"globalvar");
results.insert(results2.begin(), results2.end());
}
@@ -112,8 +114,8 @@ std::set<gd::String> EventsVariablesFinder::FindAllLayoutVariables(
const gd::Layout& layout) {
std::set<gd::String> results;
std::set<gd::String> results2 = FindArgumentsInEvents(
platform, project, layout, layout.GetEvents(), "scenevar");
std::set<gd::String> results2 = FindArgumentsInEventsAndDependencies(
platform, project, layout, "scenevar");
results.insert(results2.begin(), results2.end());
return results;
@@ -126,12 +128,12 @@ std::set<gd::String> EventsVariablesFinder::FindAllObjectVariables(
const gd::Object& object) {
std::set<gd::String> results;
std::set<gd::String> results2 = FindArgumentsInEvents(platform,
project,
layout,
layout.GetEvents(),
"objectvar",
object.GetName());
std::set<gd::String> results2 = FindArgumentsInEventsAndDependencies(
platform,
project,
layout,
"objectvar",
object.GetName());
results.insert(results2.begin(), results2.end());
return results;
@@ -203,6 +205,38 @@ std::set<gd::String> EventsVariablesFinder::FindArgumentsInInstructions(
return results;
}
std::set<gd::String> EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
const gd::Platform& platform,
const gd::Project& project,
const gd::Layout& layout,
const gd::String& parameterType,
const gd::String& objectName) {
std::set<gd::String> results;
std::set<gd::String> results2 = FindArgumentsInEvents(
platform, project, layout, layout.GetEvents(), parameterType, objectName);
results.insert(results2.begin(), results2.end());
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);
std::set<gd::String> results3 = FindArgumentsInEvents(
platform, project, layout, externalEvents.GetEvents(), parameterType, objectName);
results.insert(results3.begin(), results3.end());
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
std::set<gd::String> results3 = FindArgumentsInEvents(
platform, project, dependencyLayout, dependencyLayout.GetEvents(), parameterType, objectName);
results.insert(results3.begin(), results3.end());
}
return results;
}
std::set<gd::String> EventsVariablesFinder::FindArgumentsInEvents(
const gd::Platform& platform,
const gd::Project& project,

View File

@@ -24,11 +24,17 @@ namespace gd {
* global or object variables.
*
* \todo Refactor this class using ArbitraryEventsWorker
* \todo Rework this class to return the shapes (maybe even types?) of the
* variables (in particular for structures and arrays), so we can use this
* for better autocompletions in the variables dialogs in the IDE.
*
* \ingroup IDE
*/
class EventsVariablesFinder {
public:
EventsVariablesFinder(){};
virtual ~EventsVariablesFinder(){};
/**
* Construct a list containing the name of all global variables used in the
* project.
@@ -94,10 +100,34 @@ class EventsVariablesFinder {
/**
* Construct a list of the value of the arguments for parameters of type @
* parameterType
* parameterType. It searchs in events dependencies.
*
* \param platform The platform of the project
* \param project The project used
* \param project The layout used
* \param layout The layout used
* \param events The events to be analyzed
* \param parameterType The parameters type to be analyzed
* \param objectName If not empty, parameters will be taken into account
* only if the last object parameter is filled with
* this value.
*
* \return A std::set filled with the values used for all parameters of the
* specified type
*/
static std::set<gd::String> FindArgumentsInEventsAndDependencies(
const gd::Platform& platform,
const gd::Project& project,
const gd::Layout& layout,
const gd::String& parameterType,
const gd::String& objectName = "");
/**
* Construct a list of the value of the arguments for parameters of type @
* parameterType. It doesn't search in events dependencies.
*
* \param platform The platform of the project
* \param project The project used
* \param layout The layout used
* \param events The events to be analyzed
* \param parameterType The parameters type to be analyzed
* \param objectName If not empty, parameters will be taken into account
@@ -113,10 +143,7 @@ class EventsVariablesFinder {
const gd::Layout& layout,
const gd::EventsList& events,
const gd::String& parameterType,
const gd::String& objectName = "");
EventsVariablesFinder(){};
virtual ~EventsVariablesFinder(){};
const gd::String& objectName);
};
} // namespace gd

View File

@@ -0,0 +1,31 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "ExpressionCompletionFinder.h"
namespace gd {
const gd::ParameterMetadata
ExpressionCompletionDescription::badParameterMetadata;
/**
* \brief Turn an ExpressionCompletionDescription to a string.
*/
std::ostream& operator<<(std::ostream& os,
ExpressionCompletionDescription const& value) {
os << "{ " << value.GetCompletionKind() << ", " << value.GetType() << ", "
<< value.GetPrefix() << ", " << value.GetObjectName() << ", "
<< value.GetBehaviorName() << ", "
<< (value.IsExact() ? "exact" : "non-exact") << ", "
<< (value.IsLastParameter() ? "last parameter" : "not last parameter")
<< ", "
<< (value.HasParameterMetadata()
? gd::String::From(&value.GetParameterMetadata())
: "no parameter metadata")
<< " }";
return os;
}
} // namespace gd

View File

@@ -8,10 +8,14 @@
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
namespace gd {
class Expression;
class ObjectsContainer;
@@ -33,19 +37,21 @@ struct ExpressionCompletionDescription {
/**
* The different kind of completions that can be described.
*/
enum CompletionKind {
Object,
Behavior,
Expression,
Variable,
};
enum CompletionKind { Object, Behavior, Expression, Variable, Text };
/**
* \brief Create a completion for an object with the given prefix
*/
static ExpressionCompletionDescription ForObject(const gd::String& type_,
const gd::String& prefix_) {
return ExpressionCompletionDescription(Object, type_, prefix_);
static ExpressionCompletionDescription ForObject(
const gd::String& type_,
const gd::String& prefix_,
size_t replacementStartPosition_,
size_t replacementEndPosition_) {
return ExpressionCompletionDescription(Object,
type_,
prefix_,
replacementStartPosition_,
replacementEndPosition_);
}
/**
@@ -53,17 +59,58 @@ struct ExpressionCompletionDescription {
* the specified object
*/
static ExpressionCompletionDescription ForBehavior(
const gd::String& prefix_, const gd::String& objectName_) {
return ExpressionCompletionDescription(Behavior, "", prefix_, objectName_);
const gd::String& prefix_,
size_t replacementStartPosition_,
size_t replacementEndPosition_,
const gd::String& objectName_) {
return ExpressionCompletionDescription(Behavior,
"",
prefix_,
replacementStartPosition_,
replacementEndPosition_,
objectName_);
}
/**
* \brief Create a completion for a variable with the given prefix
*/
static ExpressionCompletionDescription ForVariable(
const gd::String& type_, const gd::String& prefix_) {
return ExpressionCompletionDescription(Variable, type_, prefix_);
const gd::String& type_,
const gd::String& prefix_,
size_t replacementStartPosition_,
size_t replacementEndPosition_,
const gd::String& objectName_ = "") {
return ExpressionCompletionDescription(Variable,
type_,
prefix_,
replacementStartPosition_,
replacementEndPosition_,
objectName_);
}
/**
* \brief Create a completion for a text with the given prefix
*/
static ExpressionCompletionDescription ForText(
const gd::String& type_,
const gd::ParameterMetadata& parameterMetadata_,
const gd::String& prefix_,
size_t replacementStartPosition_,
size_t replacementEndPosition_,
const bool isLastParameter_,
const gd::String& objectName_ = "") {
auto description =
ExpressionCompletionDescription(Text,
type_,
prefix_,
replacementStartPosition_,
replacementEndPosition_,
objectName_);
description.SetIsLastParameter(isLastParameter_);
description.SetParameterMetadata(parameterMetadata_);
return description;
}
/**
* \brief Create a completion for an expression (free, object or behavior
* expression) with the given prefix
@@ -71,10 +118,17 @@ struct ExpressionCompletionDescription {
static ExpressionCompletionDescription ForExpression(
const gd::String& type_,
const gd::String& prefix_,
size_t replacementStartPosition_,
size_t replacementEndPosition_,
const gd::String& objectName_ = "",
const gd::String& behaviorName_ = "") {
return ExpressionCompletionDescription(
Expression, type_, prefix_, objectName_, behaviorName_);
return ExpressionCompletionDescription(Expression,
type_,
prefix_,
replacementStartPosition_,
replacementEndPosition_,
objectName_,
behaviorName_);
}
/** Check if two description of completions are equal */
@@ -131,6 +185,56 @@ struct ExpressionCompletionDescription {
*/
bool IsExact() const { return isExact; }
/**
* \brief Return the first character index of the autocompleted part.
*/
size_t GetReplacementStartPosition() const {
return replacementStartPosition;
}
/**
* \brief Return the first character index after the autocompleted part.
*/
size_t GetReplacementEndPosition() const { return replacementEndPosition; }
/**
* \brief Set if the expression is the last child of a function call.
*/
ExpressionCompletionDescription& SetIsLastParameter(bool isLastParameter_) {
isLastParameter = isLastParameter_;
return *this;
}
/**
* \brief Check if the expression is the last child of a function call.
*/
bool IsLastParameter() const { return isLastParameter; }
/**
* \brief Set the parameter metadata, in the case the completion is about
* a parameter of a function call.
*/
ExpressionCompletionDescription& SetParameterMetadata(
const gd::ParameterMetadata& parameterMetadata_) {
parameterMetadata = &parameterMetadata_;
return *this;
}
/**
* \brief Check if the completion is about a parameter of a function call.
*/
bool HasParameterMetadata() const {
return parameterMetadata != &badParameterMetadata;
}
/**
* \brief Return the parameter metadata, if the completion is about a
* parameter of a function call. Returns an empty metadata otherwise.
*/
const gd::ParameterMetadata& GetParameterMetadata() const {
return *parameterMetadata;
}
/** Default constructor, only to be used by Emscripten bindings. */
ExpressionCompletionDescription() : completionKind(Object){};
@@ -138,34 +242,40 @@ struct ExpressionCompletionDescription {
ExpressionCompletionDescription(CompletionKind completionKind_,
const gd::String& type_,
const gd::String& prefix_,
size_t replacementStartPosition_,
size_t replacementEndPosition_,
const gd::String& objectName_ = "",
const gd::String& behaviorName_ = "")
: completionKind(completionKind_),
type(type_),
prefix(prefix_),
replacementStartPosition(replacementStartPosition_),
replacementEndPosition(replacementEndPosition_),
objectName(objectName_),
behaviorName(behaviorName_),
isExact(false) {}
isExact(false),
isLastParameter(false),
parameterMetadata(&badParameterMetadata) {}
CompletionKind completionKind;
gd::String type;
gd::String prefix;
size_t replacementStartPosition;
size_t replacementEndPosition;
gd::String objectName;
gd::String behaviorName;
bool isExact;
bool isLastParameter;
const gd::ParameterMetadata* parameterMetadata;
static const gd::ParameterMetadata badParameterMetadata;
};
/**
* \brief Turn an ExpressionCompletionDescription to a string.
*/
std::ostream& operator<<(std::ostream& os,
ExpressionCompletionDescription const& value) {
os << "{ " << value.GetCompletionKind() << ", " << value.GetType() << ", "
<< value.GetPrefix() << ", " << value.GetObjectName() << ", "
<< value.GetBehaviorName() << ", "
<< (value.IsExact() ? "exact" : "non-exact") << " }";
return os;
}
ExpressionCompletionDescription const& value);
/**
* \brief Returns the list of completion descriptions for an expression node.
@@ -182,16 +292,18 @@ class GD_CORE_API ExpressionCompletionFinder
static std::vector<ExpressionCompletionDescription>
GetCompletionDescriptionsFor(gd::ExpressionNode& node,
size_t searchedPosition) {
gd::ExpressionNode* nodeAtLocation =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(node,
searchedPosition);
gd::ExpressionNodeLocationFinder finder(searchedPosition);
node.Visit(finder);
gd::ExpressionNode* nodeAtLocation = finder.GetNode();
if (nodeAtLocation == nullptr) {
std::vector<ExpressionCompletionDescription> emptyCompletions;
return emptyCompletions;
}
gd::ExpressionCompletionFinder autocompletionProvider(searchedPosition);
gd::ExpressionNode* maybeParentNodeAtLocation = finder.GetParentNode();
gd::ExpressionCompletionFinder autocompletionProvider(
searchedPosition, maybeParentNodeAtLocation);
nodeAtLocation->Visit(autocompletionProvider);
return autocompletionProvider.GetCompletionDescriptions();
}
@@ -208,32 +320,92 @@ class GD_CORE_API ExpressionCompletionFinder
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, ""));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, ""));
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, "", searchedPosition + 1, searchedPosition + 1));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, "", searchedPosition + 1, searchedPosition + 1));
}
void OnVisitOperatorNode(OperatorNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, ""));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, ""));
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, "", searchedPosition + 1, searchedPosition + 1));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, "", searchedPosition + 1, searchedPosition + 1));
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, ""));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, ""));
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, "", searchedPosition + 1, searchedPosition + 1));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, "", searchedPosition + 1, searchedPosition + 1));
}
void OnVisitNumberNode(NumberNode& node) override {
// No completions
}
void OnVisitTextNode(TextNode& node) override {
// No completions
// Completions are searched in the case the text node is a parameter of a
// function call.
FunctionCallNode* functionCall =
dynamic_cast<FunctionCallNode*>(maybeParentNodeAtLocation);
if (functionCall != nullptr) {
int parameterIndex = -1;
for (int i = 0; i < functionCall->parameters.size(); i++) {
if (functionCall->parameters.at(i).get() == &node) {
parameterIndex = i;
break;
}
}
if (parameterIndex < 0) {
return;
}
// Search the parameter metadata index skipping invisible ones.
size_t visibleParameterIndex = 0;
size_t metadataParameterIndex = ExpressionParser2::WrittenParametersFirstIndex(
functionCall->objectName, functionCall->behaviorName);
const gd::ParameterMetadata* parameterMetadata = nullptr;
while (metadataParameterIndex <
functionCall->expressionMetadata.parameters.size()) {
if (!functionCall->expressionMetadata.parameters[metadataParameterIndex]
.IsCodeOnly()) {
if (visibleParameterIndex == parameterIndex) {
parameterMetadata = &functionCall->expressionMetadata
.parameters[metadataParameterIndex];
}
visibleParameterIndex++;
}
metadataParameterIndex++;
}
const int visibleParameterCount = visibleParameterIndex;
if (parameterMetadata == nullptr) {
// There are too many parameters in the expression, this text node is
// not actually linked to a parameter expected by the function call.
return;
}
const gd::String& type = parameterMetadata->GetType();
if (type == "string") {
// No completions for an arbitrary string.
return;
}
bool isLastParameter = parameterIndex == visibleParameterCount - 1;
completions.push_back(ExpressionCompletionDescription::ForText(
type,
*parameterMetadata,
node.text,
node.location.GetStartPosition(),
node.location.GetEndPosition(),
isLastParameter,
functionCall->objectName));
}
}
void OnVisitVariableNode(VariableNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForVariable(node.type, node.name));
completions.push_back(ExpressionCompletionDescription::ForVariable(
node.type,
node.name,
node.location.GetStartPosition(),
node.location.GetEndPosition(),
node.objectName));
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
// No completions
@@ -246,13 +418,22 @@ class GD_CORE_API ExpressionCompletionFinder
if (gd::ParameterMetadata::IsObject(node.type)) {
// Only show completions of objects if an object is required
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.identifierName));
node.type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
} else {
// Show completions for expressions and objects otherwise.
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.identifierName));
node.type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.identifierName));
node.type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
@@ -262,16 +443,24 @@ class GD_CORE_API ExpressionCompletionFinder
// function name missing)
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
node.type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.objectFunctionOrBehaviorName, node.objectName));
node.objectFunctionOrBehaviorName,
node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
node.objectName));
} else if (IsCaretOn(node.behaviorNameNamespaceSeparatorLocation) ||
IsCaretOn(node.behaviorFunctionNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
node.behaviorFunctionName,
node.behaviorFunctionNameLocation.GetStartPosition(),
node.behaviorFunctionNameLocation.GetEndPosition(),
node.objectName,
node.objectFunctionOrBehaviorName));
}
@@ -279,13 +468,23 @@ class GD_CORE_API ExpressionCompletionFinder
// Object function or behavior name
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
node.type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.objectFunctionOrBehaviorName, node.objectName));
node.objectFunctionOrBehaviorName,
node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
node.objectName));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.objectFunctionOrBehaviorName, node.objectName));
node.type,
node.objectFunctionOrBehaviorName,
node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
node.objectName));
}
}
}
@@ -297,24 +496,35 @@ class GD_CORE_API ExpressionCompletionFinder
// Behavior function
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
node.type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
} else if (IsCaretOn(node.objectNameDotLocation) ||
IsCaretOn(node.behaviorNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.behaviorName, node.objectName));
node.behaviorName,
node.behaviorNameLocation.GetStartPosition(),
node.behaviorNameLocation.GetEndPosition(),
node.objectName));
} else {
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type,
node.functionName,
node.objectName,
node.behaviorName)
.SetIsExact(isCaretOnParenthesis));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
node.functionName,
node.functionNameLocation.GetStartPosition(),
node.functionNameLocation.GetEndPosition(),
node.objectName,
node.behaviorName)
.SetIsExact(isCaretOnParenthesis));
}
} else if (!node.objectName.empty()) {
// Object function
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, node.objectName));
node.type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
} else {
// Add completions for behaviors, because we could imagine that the user
// wants to move from an object function to a behavior function, and so
@@ -323,25 +533,41 @@ class GD_CORE_API ExpressionCompletionFinder
// function).
if (!isCaretOnParenthesis) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.functionName, node.objectName));
node.functionName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition(),
node.objectName));
}
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.functionName, node.objectName)
node.type,
node.functionName,
node.functionNameLocation.GetStartPosition(),
node.functionNameLocation.GetEndPosition(),
node.objectName)
.SetIsExact(isCaretOnParenthesis));
}
} else {
// Free function
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, node.functionName)
node.type,
node.functionName,
node.functionNameLocation.GetStartPosition(),
node.functionNameLocation.GetEndPosition())
.SetIsExact(isCaretOnParenthesis));
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
completions.push_back(
ExpressionCompletionDescription::ForObject(node.type, node.text));
completions.push_back(
ExpressionCompletionDescription::ForExpression(node.type, node.text));
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
node.text,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
node.text,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
}
private:
@@ -354,11 +580,14 @@ class GD_CORE_API ExpressionCompletionFinder
(inclusive && searchedPosition <= location.GetEndPosition())));
}
ExpressionCompletionFinder(size_t searchedPosition_)
: searchedPosition(searchedPosition_){};
ExpressionCompletionFinder(size_t searchedPosition_,
gd::ExpressionNode* maybeParentNodeAtLocation_)
: searchedPosition(searchedPosition_),
maybeParentNodeAtLocation(maybeParentNodeAtLocation_){};
std::vector<ExpressionCompletionDescription> completions;
size_t searchedPosition;
gd::ExpressionNode* maybeParentNodeAtLocation;
};
} // namespace gd

View File

@@ -8,6 +8,7 @@
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
namespace gd {
@@ -46,11 +47,30 @@ class GD_CORE_API ExpressionNodeLocationFinder
return finder.GetNode();
}
/**
* \brief Helper function to find the parent of the deepest node at the search
* position, if any.
*
* \warning Useful for tests. In other cases, prefer using `GetParentNode`.
*/
static ExpressionNode* GetParentNodeAtPosition(gd::ExpressionNode& node,
size_t searchedPosition) {
gd::ExpressionNodeLocationFinder finder(searchedPosition);
node.Visit(finder);
return finder.GetParentNode();
}
/**
* \brief Return the deepest node found at the search position, if any.
*/
ExpressionNode* GetNode() { return foundNode; };
/**
* \brief Return the parent of deepest node found at the search position, if
* any.
*/
ExpressionNode* GetParentNode() { return parentNode; };
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
CheckSearchPositionInNode(node);
@@ -110,6 +130,7 @@ class GD_CORE_API ExpressionNodeLocationFinder
if (node.location.GetStartPosition() <= searchedPosition &&
((!inclusive && searchedPosition < node.location.GetEndPosition()) ||
(inclusive && searchedPosition <= node.location.GetEndPosition()))) {
parentNode = foundNode;
foundNode = &node;
return true;
}
@@ -119,6 +140,7 @@ class GD_CORE_API ExpressionNodeLocationFinder
size_t searchedPosition;
ExpressionNode* foundNode;
ExpressionNode* parentNode;
};
} // namespace gd

View File

@@ -24,24 +24,6 @@ namespace gd {
InstructionSentenceFormatter *InstructionSentenceFormatter::_singleton = NULL;
gd::String InstructionSentenceFormatter::Translate(
const gd::Instruction &instr, const gd::InstructionMetadata &metadata) {
gd::String out = metadata.GetSentence();
if (out.empty())
out = " "; // Prevent empty sentences that could trigger graphical
// glitches.
// Replace _PARAMx_ placeholders by their values
for (std::size_t i = 0; i < metadata.parameters.size(); ++i) {
gd::String placeholder = "_PARAM" + gd::String::From(i) + "_";
gd::String parameter = instr.GetParameter(i).GetPlainString();
out = out.FindAndReplace(placeholder, parameter);
}
out = out.FindAndReplace("\n", " ");
return out;
}
std::vector<std::pair<gd::String, gd::TextFormatting> >
InstructionSentenceFormatter::GetAsFormattedText(
const Instruction &instr, const gd::InstructionMetadata &metadata) {
@@ -83,8 +65,7 @@ InstructionSentenceFormatter::GetAsFormattedText(
}
// Add the parameter
TextFormatting format =
GetFormattingFromType(metadata.parameters[firstParamIndex].type);
TextFormatting format;
format.userData = firstParamIndex;
gd::String text = instr.GetParameter(firstParamIndex).GetPlainString();
@@ -109,74 +90,6 @@ InstructionSentenceFormatter::GetAsFormattedText(
return formattedStr;
}
TextFormatting InstructionSentenceFormatter::GetFormattingFromType(
const gd::String &type) {
if (gd::ParameterMetadata::IsObject(type)) return typesFormatting["object"];
return typesFormatting[type];
}
gd::String InstructionSentenceFormatter::LabelFromType(const gd::String &type) {
if (type.empty())
return "";
else if (type == "expression")
return _("Expression");
else if (gd::ParameterMetadata::IsObject(type))
return _("Object");
else if (type == "behavior")
return _("Behavior");
else if (type == "operator")
return _("Operator");
else if (type == "relationalOperator")
return _("Relational operator");
else if (type == "file")
return _("File");
else if (type == "key")
return _("Key");
else if (type == "mouse")
return _("Mouse button");
else if (type == "yesorno")
return _("Yes or No");
else if (type == "police")
return _("Font");
else if (type == "color")
return _("Color");
else if (type == "trueorfalse")
return _("True or False");
else if (type == "string")
return _("String");
else if (type == "musicfile")
return _("Music");
else if (type == "soundfile")
return _("Sound");
else if (type == "password")
return _("Password");
else if (type == "layer")
return _("Layer");
else if (type == "joyaxis")
return _("Joystick axis");
else if (type == "objectvar")
return _("Variable of the object");
else if (type == "scenevar")
return _("Scene variable");
else if (type == "globalvar")
return _("Global variable");
return _("Unknown");
}
void InstructionSentenceFormatter::LoadTypesFormattingFromConfig() {
// Load default configuration
typesFormatting.clear();
typesFormatting["expression"].SetColor(27, 143, 1).SetBold();
typesFormatting["object"].SetColor(182, 97, 10).SetBold();
typesFormatting["behavior"].SetColor(119, 119, 119).SetBold();
typesFormatting["operator"].SetColor(55, 131, 211).SetBold();
typesFormatting["objectvar"].SetColor(131, 55, 162).SetBold();
typesFormatting["scenevar"].SetColor(131, 55, 162).SetBold();
typesFormatting["globalvar"].SetColor(131, 55, 162).SetBold();
}
} // namespace gd
#endif

View File

@@ -25,39 +25,12 @@ namespace gd {
*/
class GD_CORE_API InstructionSentenceFormatter {
public:
/**
* \brief Create a sentence from an instruction and its metadata.
*
* Sentence is provided in the gd::InstructionMetadata passed as parameter.
* Parameters placeholders ("_PARAMx_", x being the parameter index) are
* replaced by their values stored in the isntruction passed as parameter.
*/
gd::String Translate(const gd::Instruction &instr,
const gd::InstructionMetadata &metadata);
/**
* \brief Create a formatted sentence from an instruction and its metadata.
*/
std::vector<std::pair<gd::String, gd::TextFormatting> > GetAsFormattedText(
const gd::Instruction &instr, const gd::InstructionMetadata &metadata);
/**
* \brief Return the TextFormatting object associated to the \a type.
*/
TextFormatting GetFormattingFromType(const gd::String &type);
/**
* \brief Return the label of a parameter type
*/
gd::String LabelFromType(const gd::String &type);
/**
* \brief Load the configuration from the default configuration.
*/
void LoadTypesFormattingFromConfig();
std::map<gd::String, gd::TextFormatting> typesFormatting;
static InstructionSentenceFormatter *Get() {
if (NULL == _singleton) {
_singleton = new InstructionSentenceFormatter;

View File

@@ -13,85 +13,20 @@ namespace gd {
/**
* \brief Represents the style of a text displayed in the events editor.
*
* Notably used by EventsRenderingHelper to render Instruction.
*
* \see EventsRenderingHelper
* \ingroup IDEDialogsEventsEditor
*/
class GD_CORE_API TextFormatting {
public:
TextFormatting()
: colorRed(0),
colorGreen(0),
colorBlue(0),
bold(false),
italic(false),
userData(gd::String::npos) {}
TextFormatting() : userData(gd::String::npos) {}
~TextFormatting() {}
/**
* \brief Return true if the bold style must be applied.
*/
bool IsBold() const { return bold; }
/**
* \brief Return true if the italic style must be applied.
*/
bool IsItalic() const { return italic; }
/**
* \brief Return the red component of the color that must be applied to the
* text.
*/
unsigned int GetColorRed() const { return colorRed; }
/**
* \brief Return the green component of the color that must be applied to the
* text.
*/
unsigned int GetColorGreen() const { return colorGreen; }
/**
* \brief Return the blue component of the color that must be applied to the
* text.
*/
unsigned int GetColorBlue() const { return colorBlue; }
/**
* Change the color of the text.
*/
TextFormatting& SetColor(unsigned int r, unsigned int g, unsigned int b) {
colorRed = r;
colorGreen = g;
colorBlue = b;
return *this;
}
/**
* \brief Set if the bold style must be applied.
*/
TextFormatting& SetBold(bool enable = true) {
bold = enable;
return *this;
}
/**
* \brief Set if the italic style must be applied.
*/
TextFormatting& SetItalic(bool enable = true) {
italic = enable;
return *this;
}
/**
* Return the data (an integer) associated with the text formatting.
* Used to store the parameter when rendering instructions.
*/
size_t GetUserData() const { return userData; }
unsigned int colorRed;
unsigned int colorGreen;
unsigned int colorBlue;
bool bold;
bool italic;
size_t userData;
};

View File

@@ -14,16 +14,16 @@ namespace gd {
* \note Both objects must be kept alive, as this is keeping a pointer to them.
*/
struct DependencyMetadataAndExtension {
DependencyMetadataAndExtension(gd::DependencyMetadata& dependency_,
gd::PlatformExtension& extension_)
DependencyMetadataAndExtension(gd::DependencyMetadata &dependency_,
gd::PlatformExtension &extension_)
: dependency(&dependency_), extension(&extension_){};
gd::DependencyMetadata& GetDependency() const { return *dependency; };
gd::PlatformExtension& GetExtension() const { return *extension; };
gd::DependencyMetadata &GetDependency() const { return *dependency; };
gd::PlatformExtension &GetExtension() const { return *extension; };
private:
gd::DependencyMetadata* dependency;
gd::PlatformExtension* extension;
gd::DependencyMetadata *dependency;
gd::PlatformExtension *extension;
};
/**
@@ -33,8 +33,8 @@ struct DependencyMetadataAndExtension {
class ExportedDependencyResolver {
public:
/**
* \brief Return the list of dependencies to be exported for the given project
* and dependency type.
* \brief Return the list of dependencies to be exported for the given
* project, used extensions list and dependency type.
*
* Not all dependencies declared by extensions must be exported: some are only
* exported when some settings are filled. Then, some others are only exported
@@ -42,11 +42,13 @@ class ExportedDependencyResolver {
* one level though).
*/
static std::vector<DependencyMetadataAndExtension> GetDependenciesFor(
const gd::Project& project, const gd::String& dependencyType) {
const gd::Project &project,
std::set<gd::String> usedExtensions,
const gd::String &dependencyType) {
std::vector<DependencyMetadataAndExtension> dependenciesWithProperType;
for (std::shared_ptr<gd::PlatformExtension> extension :
project.GetCurrentPlatform().GetAllPlatformExtensions()) {
for (gd::DependencyMetadata& dependency :
for (const gd::String &extensionName : usedExtensions) {
auto extension = project.GetCurrentPlatform().GetExtension(extensionName);
for (gd::DependencyMetadata &dependency :
extension->GetAllDependencies()) {
if (dependency.GetDependencyType() == dependencyType) {
DependencyMetadataAndExtension dependencyMetadataAndExtension(
@@ -60,7 +62,7 @@ class ExportedDependencyResolver {
// and those that don't require extra settings to be filled.
std::vector<DependencyMetadataAndExtension> dependenciesWithFilledSettings;
for (auto dependencyAndExtension : dependenciesWithProperType) {
auto& dependency = dependencyAndExtension.GetDependency();
auto &dependency = dependencyAndExtension.GetDependency();
auto extraSettingValues = GetExtensionDependencyExtraSettingValues(
project, dependencyAndExtension);
@@ -73,15 +75,15 @@ class ExportedDependencyResolver {
// exported (or dependencies that don't require another dependency).
std::vector<DependencyMetadataAndExtension> exportedDependencies;
for (auto dependencyAndExtension : dependenciesWithFilledSettings) {
auto& dependency = dependencyAndExtension.GetDependency();
auto& otherDependencyName =
auto &dependency = dependencyAndExtension.GetDependency();
auto &otherDependencyName =
dependency.GetOtherDependencyThatMustBeExported();
if (otherDependencyName.empty() ||
std::find_if(
dependenciesWithFilledSettings.begin(),
dependenciesWithFilledSettings.end(),
[&otherDependencyName](
DependencyMetadataAndExtension& otherDependencyAndExtension) {
DependencyMetadataAndExtension &otherDependencyAndExtension) {
return otherDependencyAndExtension.GetDependency().GetName() ==
otherDependencyName;
}) != dependenciesWithFilledSettings.end()) {
@@ -98,15 +100,15 @@ class ExportedDependencyResolver {
*/
static std::map<gd::String, gd::String>
GetExtensionDependencyExtraSettingValues(
const gd::Project& project,
const gd::DependencyMetadataAndExtension& dependencyAndExtension) {
const gd::Project &project,
const gd::DependencyMetadataAndExtension &dependencyAndExtension) {
std::map<gd::String, gd::String> values;
auto& dependency = dependencyAndExtension.GetDependency();
const gd::String& extensionName =
auto &dependency = dependencyAndExtension.GetDependency();
const gd::String &extensionName =
dependencyAndExtension.GetExtension().GetName();
for (const auto& extraSetting : dependency.GetAllExtraSettings()) {
const gd::String& type = extraSetting.second.GetType();
for (const auto &extraSetting : dependency.GetAllExtraSettings()) {
const gd::String &type = extraSetting.second.GetType();
const gd::String extraSettingValue =
type == "ExtensionProperty"
? project.GetExtensionProperties().GetValue(

View File

@@ -9,7 +9,6 @@
namespace gd {
#if defined(GD_IDE_ONLY)
void Effect::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());
element.SetAttribute("effectType", GetEffectType());
@@ -32,7 +31,6 @@ void Effect::SerializeTo(SerializerElement& element) const {
booleanParametersElement.AddChild(parameter.first)
.SetValue(parameter.second);
}
#endif
void Effect::UnserializeFrom(const SerializerElement& element) {
SetName(element.GetStringAttribute("name"));

View File

@@ -74,12 +74,10 @@ class GD_CORE_API Effect {
booleanParameters.clear();
}
#if defined(GD_IDE_ONLY)
/**
* \brief Serialize layer.
*/
void SerializeTo(SerializerElement& element) const;
#endif
/**
* \brief Unserialize the layer.

View File

@@ -0,0 +1,140 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "EffectsContainer.h"
#include "Effect.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
Effect EffectsContainer::badEffect;
EffectsContainer::EffectsContainer() {}
EffectsContainer::EffectsContainer(const EffectsContainer& other) {
Init(other);
}
EffectsContainer& EffectsContainer::operator=(const EffectsContainer& rhs) {
if (this != &rhs) Init(rhs);
return *this;
}
void EffectsContainer::Init(const EffectsContainer& other) {
effects.clear();
for (auto& it : other.effects) {
effects.push_back(std::make_shared<Effect>(*it));
}
}
bool EffectsContainer::HasEffectNamed(const gd::String& name) const {
return (find_if(effects.begin(),
effects.end(),
[&name](const std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
}) != effects.end());
}
gd::Effect& EffectsContainer::GetEffect(const gd::String& name) {
auto effect = find_if(effects.begin(),
effects.end(),
[&name](std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
});
if (effect != effects.end()) return **effect;
return badEffect;
}
const gd::Effect& EffectsContainer::GetEffect(const gd::String& name) const {
auto effect = find_if(effects.begin(),
effects.end(),
[&name](const std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
});
if (effect != effects.end()) return **effect;
return badEffect;
}
gd::Effect& EffectsContainer::GetEffect(std::size_t index) {
return *effects[index];
}
const gd::Effect& EffectsContainer::GetEffect(std::size_t index) const {
return *effects[index];
}
std::size_t EffectsContainer::GetEffectsCount() const { return effects.size(); }
std::size_t EffectsContainer::GetEffectPosition(const gd::String& name) const {
for (std::size_t i = 0; i < effects.size(); ++i) {
if (effects[i]->GetName() == name) return i;
}
return gd::String::npos;
}
gd::Effect& EffectsContainer::InsertNewEffect(const gd::String& name,
std::size_t position) {
auto newEffect = std::make_shared<Effect>();
newEffect->SetName(name);
if (position < effects.size())
effects.insert(effects.begin() + position, newEffect);
else
effects.push_back(newEffect);
return *newEffect;
}
void EffectsContainer::InsertEffect(const gd::Effect& effect,
std::size_t position) {
auto newEffect = std::make_shared<gd::Effect>(effect);
if (position < effects.size())
effects.insert(effects.begin() + position, newEffect);
else
effects.push_back(newEffect);
}
void EffectsContainer::RemoveEffect(const gd::String& name) {
auto effect = find_if(effects.begin(),
effects.end(),
[&name](const std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
});
if (effect == effects.end()) return;
effects.erase(effect);
}
void EffectsContainer::SwapEffects(std::size_t firstEffectIndex,
std::size_t secondEffectIndex) {
if (firstEffectIndex >= effects.size() || secondEffectIndex >= effects.size())
return;
auto temp = effects[firstEffectIndex];
effects[firstEffectIndex] = effects[secondEffectIndex];
effects[secondEffectIndex] = temp;
}
void EffectsContainer::SerializeTo(SerializerElement& element) const {
element.ConsiderAsArrayOf("effect");
for (std::size_t i = 0; i < GetEffectsCount(); ++i) {
SerializerElement& effectElement = element.AddChild("effect");
GetEffect(i).SerializeTo(effectElement);
}
}
void EffectsContainer::UnserializeFrom(const SerializerElement& element) {
effects.clear();
element.ConsiderAsArrayOf("effect");
for (std::size_t i = 0; i < element.GetChildrenCount(); ++i) {
const SerializerElement& effectElement = element.GetChild(i);
auto effect = std::make_shared<Effect>();
effect->UnserializeFrom(effectElement);
effects.push_back(effect);
}
}
} // namespace gd

View File

@@ -0,0 +1,114 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EFFECTS_CONTAINER_H
#define GDCORE_EFFECTS_CONTAINER_H
#include <memory>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Effect;
}
namespace gd {
class SerializerElement;
}
namespace gd {
/**
* \brief Contains effects applied to an entity on screen (i.e: a Layer or an
* Object).
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API EffectsContainer {
public:
EffectsContainer();
EffectsContainer(const EffectsContainer& other);
virtual ~EffectsContainer(){};
EffectsContainer& operator=(const EffectsContainer& rhs);
/**
* \brief Return true if the effect called "name" exists.
*/
bool HasEffectNamed(const gd::String& name) const;
/**
* \brief Return a reference to the effect called "name".
*/
Effect& GetEffect(const gd::String& name);
/**
* \brief Return a reference to the effect called "name".
*/
const Effect& GetEffect(const gd::String& name) const;
/**
* Return a reference to the effect at position "index" in the effects list
*/
Effect& GetEffect(std::size_t index);
/**
* Return a reference to the effect at position "index" in the effects list
*/
const Effect& GetEffect(std::size_t index) const;
/**
* Return the position of the effect called "name" in the effects list
*/
std::size_t GetEffectPosition(const gd::String& name) const;
/**
* Return the number of effecst.
*/
std::size_t GetEffectsCount() const;
/**
* Add a new effect at the specified position in the effects list.
*/
gd::Effect& InsertNewEffect(const gd::String& name, std::size_t position);
/**
* \brief Add a copy of the specified effect in the effects list.
*
* \note No pointer or reference must be kept on the effect passed as
* parameter.
*
* \param theEffect The effect that must be copied and inserted
* into the effects list
* \param position Insertion position.
*/
void InsertEffect(const Effect& theEffect, std::size_t position);
/**
* Remove the specified effect.
*/
void RemoveEffect(const gd::String& name);
/**
* Swap the position of two effects.
*/
void SwapEffects(std::size_t firstEffectIndex, std::size_t secondEffectIndex);
/**
* \brief Serialize the effects container.
*/
void SerializeTo(SerializerElement& element) const;
/**
* \brief Unserialize the effects container.
*/
void UnserializeFrom(const SerializerElement& element);
private:
std::vector<std::shared_ptr<gd::Effect>> effects;
static Effect badEffect;
void Init(const EffectsContainer& other);
};
} // namespace gd
#endif

View File

@@ -4,6 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/Layer.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Project/Effect.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -11,9 +12,9 @@
namespace gd {
Camera Layer::badCamera;
Effect Layer::badEffect;
Layer::Layer() : isVisible(true), isLightingLayer(false), followBaseLayerCamera(false) {}
Layer::Layer()
: isVisible(true), isLightingLayer(false), followBaseLayerCamera(false) {}
/**
* Change cameras count, automatically adding/removing them.
@@ -52,11 +53,7 @@ void Layer::SerializeTo(SerializerElement& element) const {
}
SerializerElement& effectsElement = element.AddChild("effects");
effectsElement.ConsiderAsArrayOf("effect");
for (std::size_t i = 0; i < GetEffectsCount(); ++i) {
SerializerElement& effectElement = effectsElement.AddChild("effect");
GetEffect(i).SerializeTo(effectElement);
}
effectsContainer.SerializeTo(effectsElement);
}
#endif
@@ -67,155 +64,53 @@ void Layer::UnserializeFrom(const SerializerElement& element) {
SetName(element.GetStringAttribute("name", "", "Name"));
SetVisibility(element.GetBoolAttribute("visibility", true, "Visibility"));
SetLightingLayer(element.GetBoolAttribute("isLightingLayer", false));
SetFollowBaseLayerCamera(element.GetBoolAttribute("followBaseLayerCamera", false));
SetAmbientLightColor(element.GetIntAttribute("ambientLightColorR", 200),
SetFollowBaseLayerCamera(
element.GetBoolAttribute("followBaseLayerCamera", false));
SetAmbientLightColor(element.GetIntAttribute("ambientLightColorR", 200),
element.GetIntAttribute("ambientLightColorG", 200),
element.GetIntAttribute("ambientLightColorB", 200));
// Compatibility with GD <= 3.3
if (element.HasChild("Camera")) {
for (std::size_t i = 0; i < element.GetChildrenCount("Camera"); ++i) {
const SerializerElement& cameraElement = element.GetChild("Camera", i);
SetCameraCount(GetCameraCount() + 1);
Camera& camera = GetCamera(GetCameraCount() - 1);
cameras.clear();
SerializerElement& camerasElement = element.GetChild("cameras");
camerasElement.ConsiderAsArrayOf("camera");
for (std::size_t i = 0; i < camerasElement.GetChildrenCount(); ++i) {
const SerializerElement& cameraElement = camerasElement.GetChild(i);
camera.SetUseDefaultSize(
cameraElement.GetBoolAttribute("DefaultSize", true));
camera.SetSize(cameraElement.GetDoubleAttribute("Width"),
cameraElement.GetDoubleAttribute("Height"));
Camera camera;
camera.SetUseDefaultSize(
cameraElement.GetBoolAttribute("defaultSize", true));
camera.SetSize(cameraElement.GetDoubleAttribute("width"),
cameraElement.GetDoubleAttribute("height"));
camera.SetUseDefaultViewport(
cameraElement.GetBoolAttribute("defaultViewport", true));
camera.SetViewport(
cameraElement.GetDoubleAttribute("viewportLeft"),
cameraElement.GetDoubleAttribute("viewportTop"),
cameraElement.GetDoubleAttribute("viewportRight"),
cameraElement.GetDoubleAttribute(
"viewportBottom"));
camera.SetUseDefaultViewport(
cameraElement.GetBoolAttribute("DefaultViewport", true));
camera.SetViewport(
cameraElement.GetDoubleAttribute("ViewportLeft"),
cameraElement.GetDoubleAttribute("ViewportTop"),
cameraElement.GetDoubleAttribute("ViewportRight"),
cameraElement.GetDoubleAttribute(
"ViewportBottom")); // (sf::Rect used Right and Bottom instead of
// Width and Height before)
}
}
// End of compatibility code
else {
SerializerElement& camerasElement = element.GetChild("cameras");
camerasElement.ConsiderAsArrayOf("camera");
for (std::size_t i = 0; i < camerasElement.GetChildrenCount(); ++i) {
const SerializerElement& cameraElement = camerasElement.GetChild(i);
SetCameraCount(GetCameraCount() + 1);
Camera& camera = GetCamera(GetCameraCount() - 1);
camera.SetUseDefaultSize(
cameraElement.GetBoolAttribute("defaultSize", true));
camera.SetSize(cameraElement.GetDoubleAttribute("width"),
cameraElement.GetDoubleAttribute("height"));
camera.SetUseDefaultViewport(
cameraElement.GetBoolAttribute("defaultViewport", true));
camera.SetViewport(
cameraElement.GetDoubleAttribute("viewportLeft"),
cameraElement.GetDoubleAttribute("viewportTop"),
cameraElement.GetDoubleAttribute("viewportRight"),
cameraElement.GetDoubleAttribute(
"viewportBottom")); // (sf::Rect used Right and Bottom instead of
// Width and Height before)
}
cameras.push_back(camera);
}
effects.clear();
SerializerElement& effectsElement = element.GetChild("effects");
effectsElement.ConsiderAsArrayOf("effect");
for (std::size_t i = 0; i < effectsElement.GetChildrenCount(); ++i) {
const SerializerElement& effectElement = effectsElement.GetChild(i);
auto effect = std::make_shared<Effect>();
effect->UnserializeFrom(effectElement);
effects.push_back(effect);
if (camerasElement.GetChildrenCount() > 50) {
// Highly unlikely that we want as many cameras, as they were not even exposed in
// the editor nor used in the game engine. Must be because of a bug in the editor that
// duplicated cameras when cancelling changes on a layer.
// Reset to one camera.
SetCameraCount(1);
}
const SerializerElement& effectsElement = element.GetChild("effects");
effectsContainer.UnserializeFrom(effectsElement);
}
gd::Effect& Layer::GetEffect(const gd::String& name) {
auto effect = find_if(effects.begin(),
effects.end(),
[&name](std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
});
if (effect != effects.end()) return **effect;
return badEffect;
}
const gd::Effect& Layer::GetEffect(const gd::String& name) const {
auto effect = find_if(effects.begin(),
effects.end(),
[&name](const std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
});
if (effect != effects.end()) return **effect;
return badEffect;
}
gd::Effect& Layer::GetEffect(std::size_t index) { return *effects[index]; }
const gd::Effect& Layer::GetEffect(std::size_t index) const {
return *effects[index];
}
std::size_t Layer::GetEffectsCount() const { return effects.size(); }
bool Layer::HasEffectNamed(const gd::String& name) const {
return (find_if(effects.begin(),
effects.end(),
[&name](const std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
}) != effects.end());
}
std::size_t Layer::GetEffectPosition(const gd::String& name) const {
for (std::size_t i = 0; i < effects.size(); ++i) {
if (effects[i]->GetName() == name) return i;
}
return gd::String::npos;
gd::EffectsContainer& Layer::GetEffects() {
return effectsContainer;
}
gd::Effect& Layer::InsertNewEffect(const gd::String& name,
std::size_t position) {
auto newEffect = std::make_shared<Effect>();
newEffect->SetName(name);
if (position < effects.size())
effects.insert(effects.begin() + position, newEffect);
else
effects.push_back(newEffect);
return *newEffect;
}
void Layer::InsertEffect(const gd::Effect& effect, std::size_t position) {
auto newEffect = std::make_shared<gd::Effect>(effect);
if (position < effects.size())
effects.insert(effects.begin() + position, newEffect);
else
effects.push_back(newEffect);
}
void Layer::RemoveEffect(const gd::String& name) {
auto effect = find_if(effects.begin(),
effects.end(),
[&name](const std::shared_ptr<gd::Effect>& effect) {
return effect->GetName() == name;
});
if (effect == effects.end()) return;
effects.erase(effect);
}
void Layer::SwapEffects(std::size_t firstEffectIndex,
std::size_t secondEffectIndex) {
if (firstEffectIndex >= effects.size() || secondEffectIndex >= effects.size())
return;
auto temp = effects[firstEffectIndex];
effects[firstEffectIndex] = effects[secondEffectIndex];
effects[secondEffectIndex] = temp;
const gd::EffectsContainer& Layer::GetEffects() const {
return effectsContainer;
}
Camera::Camera()

View File

@@ -7,7 +7,10 @@
#define GDCORE_LAYER_H
#include <memory>
#include <vector>
#include "EffectsContainer.h"
#include "GDCore/String.h"
namespace gd {
class Effect;
}
@@ -17,6 +20,9 @@ class Camera;
namespace gd {
class SerializerElement;
}
namespace gd {
class EffectsContainer;
}
namespace gd {
@@ -54,7 +60,9 @@ class GD_CORE_API Layer {
/**
* \brief Set if the layer is a lightining layer or not.
*/
void SetLightingLayer(bool isLightingLayer_) { isLightingLayer = isLightingLayer_; }
void SetLightingLayer(bool isLightingLayer_) {
isLightingLayer = isLightingLayer_;
}
/**
* \brief Return true if the layer is a lighting layer.
@@ -64,7 +72,9 @@ class GD_CORE_API Layer {
/**
* \brief Set if the layer automatically follows the base layer or not.
*/
void SetFollowBaseLayerCamera(bool followBaseLayerCamera_) { followBaseLayerCamera = followBaseLayerCamera_; }
void SetFollowBaseLayerCamera(bool followBaseLayerCamera_) {
followBaseLayerCamera = followBaseLayerCamera_;
}
/**
* \brief Return true if the layer follows the base layer.
@@ -144,66 +154,14 @@ class GD_CORE_API Layer {
*/
///@{
/**
* \brief Return true if the effect called "name" exists.
* \brief Return the effects container.
*/
bool HasEffectNamed(const gd::String& name) const;
EffectsContainer& GetEffects();
/**
* \brief Return a reference to the effect called "name".
* \brief Return a const reference to the effects container.
*/
Effect& GetEffect(const gd::String& name);
/**
* \brief Return a reference to the effect called "name".
*/
const Effect& GetEffect(const gd::String& name) const;
/**
* Return a reference to the effect at position "index" in the effects list
*/
Effect& GetEffect(std::size_t index);
/**
* Return a reference to the effect at position "index" in the effects list
*/
const Effect& GetEffect(std::size_t index) const;
/**
* Return the position of the effect called "name" in the effects list
*/
std::size_t GetEffectPosition(const gd::String& name) const;
/**
* Return the number of effecst.
*/
std::size_t GetEffectsCount() const;
/**
* Add a new effect at the specified position in the effects list.
*/
gd::Effect& InsertNewEffect(const gd::String& name, std::size_t position);
/**
* \brief Add a copy of the specified effect in the effects list.
*
* \note No pointer or reference must be kept on the layer passed as
* parameter.
*
* \param theEffect The effect that must be copied and inserted
* into the effects list
* \param position Insertion position.
*/
void InsertEffect(const Effect& theEffect, std::size_t position);
/**
* Remove the specified effect.
*/
void RemoveEffect(const gd::String& name);
/**
* Swap the position of two effects.
*/
void SwapEffects(std::size_t firstEffectIndex, std::size_t secondEffectIndex);
const EffectsContainer& GetEffects() const;
///@}
#if defined(GD_IDE_ONLY)
@@ -219,19 +177,19 @@ class GD_CORE_API Layer {
void UnserializeFrom(const SerializerElement& element);
private:
gd::String name; ///< The name of the layer
bool isVisible; ///< True if the layer is visible
bool isLightingLayer; ///< True if the layer is used to display lights and renders an ambient light.
bool followBaseLayerCamera; ///< True if the layer automatically follows the base layer
unsigned int ambientLightColorR; ///< Ambient light color Red component
unsigned int ambientLightColorG; ///< Ambient light color Green component
unsigned int ambientLightColorB; ///< Ambient light color Blue component
gd::String name; ///< The name of the layer
bool isVisible; ///< True if the layer is visible
bool isLightingLayer; ///< True if the layer is used to display lights and
///< renders an ambient light.
bool followBaseLayerCamera; ///< True if the layer automatically follows the
///< base layer
unsigned int ambientLightColorR; ///< Ambient light color Red component
unsigned int ambientLightColorG; ///< Ambient light color Green component
unsigned int ambientLightColorB; ///< Ambient light color Blue component
std::vector<gd::Camera> cameras; ///< The camera displayed by the layer
std::vector<std::shared_ptr<gd::Effect>>
effects; ///< The effects applied to the layer.
gd::EffectsContainer effectsContainer; ///< The effects applied to the layer.
static gd::Camera badCamera;
static gd::Effect badEffect;
};
/**

View File

@@ -5,10 +5,14 @@
*/
#include "Layout.h"
#include <algorithm>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Serialization.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/IDE/SceneNameMangler.h"
#include "GDCore/Project/Behavior.h"
@@ -219,16 +223,21 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
++i) {
const gd::String& name = allBehaviorsNames[i];
if (behaviorsSharedData.find(name) == behaviorsSharedData.end()) {
gd::BehaviorsSharedData* behaviorSharedData =
project.GetBehaviorSharedDatas(allBehaviorsTypes[i]);
if (behaviorSharedData) {
auto behaviorContent =
gd::make_unique<gd::BehaviorContent>(name, allBehaviorsTypes[i]);
behaviorSharedData->InitializeContent(behaviorContent->GetContent());
behaviorsSharedData[name] = std::move(behaviorContent);
}
}
if (behaviorsSharedData.find(name) != behaviorsSharedData.end()) continue;
const gd::BehaviorMetadata& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(project.GetCurrentPlatform(),
allBehaviorsTypes[i]);
if (gd::MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) continue;
gd::BehaviorsSharedData* behaviorSharedData =
behaviorMetadata.GetSharedDataInstance();
if (!behaviorSharedData) continue;
auto behaviorContent =
gd::make_unique<gd::BehaviorContent>(name, allBehaviorsTypes[i]);
behaviorSharedData->InitializeContent(behaviorContent->GetContent());
behaviorsSharedData[name] = std::move(behaviorContent);
}
// Remove useless shared data:

View File

@@ -5,15 +5,63 @@
*/
#include "LoadingScreen.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
LoadingScreen::LoadingScreen()
: showGDevelopSplash(true),
gdevelopLogoStyle("light"),
backgroundImageResourceName(""),
backgroundColor(0),
backgroundFadeInDuration(0.2),
minDuration(1.5),
logoAndProgressFadeInDuration(0.2),
logoAndProgressLogoFadeInDelay(0.2),
showProgressBar(true),
progressBarMinWidth(40),
progressBarMaxWidth(200),
progressBarWidthPercent(30),
progressBarHeight(20),
progressBarColor(0xFFFFFF){};
void LoadingScreen::SerializeTo(SerializerElement& element) const {
element.SetAttribute("showGDevelopSplash", showGDevelopSplash);
element.SetAttribute("gdevelopLogoStyle",
gdevelopLogoStyle);
element.SetAttribute("backgroundImageResourceName",
backgroundImageResourceName);
element.SetAttribute("backgroundColor", backgroundColor);
element.SetAttribute("backgroundFadeInDuration", backgroundFadeInDuration);
element.SetAttribute("minDuration", minDuration);
element.SetAttribute("logoAndProgressFadeInDuration", logoAndProgressFadeInDuration);
element.SetAttribute("logoAndProgressLogoFadeInDelay", logoAndProgressLogoFadeInDelay);
element.SetAttribute("showProgressBar", showProgressBar);
element.SetAttribute("progressBarMinWidth", progressBarMinWidth);
element.SetAttribute("progressBarMaxWidth", progressBarMaxWidth);
element.SetAttribute("progressBarWidthPercent", progressBarWidthPercent);
element.SetAttribute("progressBarHeight", progressBarHeight);
element.SetAttribute("progressBarColor", progressBarColor);
}
void LoadingScreen::UnserializeFrom(const SerializerElement& element) {
showGDevelopSplash = element.GetBoolAttribute("showGDevelopSplash", true);
gdevelopLogoStyle =
element.GetStringAttribute("gdevelopLogoStyle", "light");
backgroundImageResourceName =
element.GetStringAttribute("backgroundImageResourceName");
backgroundColor = element.GetIntAttribute("backgroundColor", 0);
backgroundFadeInDuration =
element.GetDoubleAttribute("backgroundFadeInDuration", 0.2);
minDuration = element.GetDoubleAttribute("minDuration", 1.5);
logoAndProgressFadeInDuration = element.GetDoubleAttribute("logoAndProgressFadeInDuration", 0.2);
logoAndProgressLogoFadeInDelay = element.GetDoubleAttribute("logoAndProgressLogoFadeInDelay", 0.2);
showProgressBar = element.GetBoolAttribute("showProgressBar", true);
progressBarMinWidth = element.GetDoubleAttribute("progressBarMinWidth", 40);
progressBarMaxWidth = element.GetDoubleAttribute("progressBarMaxWidth", 200);
progressBarWidthPercent = element.GetDoubleAttribute("progressBarWidthPercent", 30);
progressBarHeight = element.GetDoubleAttribute("progressBarHeight", 20);
progressBarColor = element.GetIntAttribute("progressBarColor", 0xFFFFFF);
}
} // namespace gd

View File

@@ -22,36 +22,151 @@ namespace gd {
*/
class GD_CORE_API LoadingScreen {
public:
LoadingScreen(){};
LoadingScreen();
virtual ~LoadingScreen(){};
/**
* \brief Set if the GDevelop splash should be shown while loading assets.
*/
void ShowGDevelopSplash(bool show) { showGDevelopSplash = show; };
/**
* \brief Return true if the GDevelop splash should be shown while loading
* \brief Return true if the GDevelop logo should be shown while loading
* assets.
*/
bool IsGDevelopSplashShown() const { return showGDevelopSplash; };
/**
* \brief Set if the GDevelop logo should be shown while loading assets.
*/
LoadingScreen& ShowGDevelopSplash(bool show) {
showGDevelopSplash = show;
return *this;
};
const gd::String& GetGDevelopLogoStyle() const { return gdevelopLogoStyle; };
LoadingScreen& SetGDevelopLogoStyle(const gd::String& value) {
gdevelopLogoStyle = value;
return *this;
}
const gd::String& GetBackgroundImageResourceName() const {
return backgroundImageResourceName;
};
LoadingScreen& SetBackgroundImageResourceName(const gd::String& value) {
backgroundImageResourceName = value;
return *this;
}
int GetBackgroundColor() const { return backgroundColor; };
LoadingScreen& SetBackgroundColor(int value) {
backgroundColor = value;
return *this;
}
double GetBackgroundFadeInDuration() const {
return backgroundFadeInDuration;
};
LoadingScreen& SetBackgroundFadeInDuration(double value) {
backgroundFadeInDuration = value;
return *this;
}
double GetMinDuration() const { return minDuration; };
LoadingScreen& SetMinDuration(double value) {
minDuration = value;
return *this;
}
double GetLogoAndProgressFadeInDuration() const {
return logoAndProgressFadeInDuration;
}
LoadingScreen& SetLogoAndProgressFadeInDuration(double value) {
logoAndProgressFadeInDuration = value;
return *this;
}
double GetLogoAndProgressLogoFadeInDelay() const {
return logoAndProgressLogoFadeInDelay;
}
LoadingScreen& SetLogoAndProgressLogoFadeInDelay(double value) {
logoAndProgressLogoFadeInDelay = value;
return *this;
}
bool GetShowProgressBar() const { return showProgressBar; }
LoadingScreen& SetShowProgressBar(bool value) {
showProgressBar = value;
return *this;
}
double GetProgressBarMinWidth() const { return progressBarMinWidth; }
LoadingScreen& SetProgressBarMinWidth(double value) {
progressBarMinWidth = value;
return *this;
}
double GetProgressBarMaxWidth() const { return progressBarMaxWidth; }
LoadingScreen& SetProgressBarMaxWidth(double value) {
progressBarMaxWidth = value;
return *this;
}
double GetProgressBarWidthPercent() const { return progressBarWidthPercent; }
LoadingScreen& SetProgressBarWidthPercent(double value) {
progressBarWidthPercent = value;
return *this;
}
double GetProgressBarHeight() const { return progressBarHeight; }
LoadingScreen& SetProgressBarHeight(double value) {
progressBarHeight = value;
return *this;
}
int GetProgressBarColor() const { return progressBarColor; }
LoadingScreen& SetProgressBarColor(int value) {
progressBarColor = value;
return *this;
}
/** \name Saving and loading
*/
///@{
/**
* \brief Serialize objects groups container.
* \brief Serialize the loading screen setup.
*/
void SerializeTo(SerializerElement& element) const;
/**
* \brief Unserialize the objects groups container.
* \brief Unserialize the loading screen setup.
*/
void UnserializeFrom(const SerializerElement& element);
///@}
private:
bool showGDevelopSplash;
gd::String gdevelopLogoStyle;
gd::String backgroundImageResourceName;
int backgroundColor;
double backgroundFadeInDuration; // In seconds.
double minDuration; // In seconds.
double logoAndProgressFadeInDuration; // In seconds.
double logoAndProgressLogoFadeInDelay; // In seconds.
bool showProgressBar;
double progressBarMinWidth; // In pixels.
double progressBarMaxWidth; // In pixels.
double progressBarWidthPercent;
double progressBarHeight; // In pixels.
int progressBarColor;
};
} // namespace gd

View File

@@ -4,6 +4,9 @@
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/Object.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Layout.h"
@@ -24,6 +27,7 @@ void Object::Init(const gd::Object& object) {
type = object.type;
objectVariables = object.objectVariables;
tags = object.tags;
effectsContainer = object.effectsContainer;
behaviors.clear();
for (auto& it : object.behaviors) {
@@ -85,16 +89,17 @@ std::map<gd::String, gd::PropertyDescriptor> Object::GetProperties() const {
gd::BehaviorContent* Object::AddNewBehavior(gd::Project& project,
const gd::String& type,
const gd::String& name) {
gd::Behavior* behavior = project.GetCurrentPlatform().GetBehavior(type);
if (behavior) {
auto behaviorContent = gd::make_unique<gd::BehaviorContent>(name, type);
behavior->InitializeContent(behaviorContent->GetContent());
behaviors[name] = std::move(behaviorContent);
return behaviors[name].get();
} else {
const gd::BehaviorMetadata& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(project.GetCurrentPlatform(),
type);
if (gd::MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
return nullptr;
}
auto behaviorContent = gd::make_unique<gd::BehaviorContent>(name, type);
behaviorMetadata.Get().InitializeContent(behaviorContent->GetContent());
behaviors[name] = std::move(behaviorContent);
return behaviors[name].get();
}
std::map<gd::String, gd::PropertyDescriptor>
@@ -116,6 +121,11 @@ void Object::UnserializeFrom(gd::Project& project,
element.GetChild("variables", 0, "Variables"));
behaviors.clear();
if (element.HasChild("effects")) {
const SerializerElement& effectsElement = element.GetChild("effects");
effectsContainer.UnserializeFrom(effectsElement);
}
// Compatibility with GD <= 3.3
if (element.HasChild("Automatism")) {
for (std::size_t i = 0; i < element.GetChildrenCount("Automatism"); ++i) {
@@ -180,6 +190,7 @@ void Object::SerializeTo(SerializerElement& element) const {
element.SetAttribute("type", GetType());
element.SetAttribute("tags", GetTags());
objectVariables.SerializeTo(element.AddChild("variables"));
effectsContainer.SerializeTo(element.AddChild("effects"));
SerializerElement& behaviorsElement = element.AddChild("behaviors");
behaviorsElement.ConsiderAsArrayOf("behavior");

View File

@@ -9,7 +9,9 @@
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Project/BehaviorContent.h"
#include "GDCore/Project/EffectsContainer.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/MakeUnique.h"
@@ -20,6 +22,7 @@ class Layout;
class ArbitraryResourceWorker;
class InitialInstance;
class SerializerElement;
class EffectsContainer;
} // namespace gd
namespace gd {
@@ -146,8 +149,7 @@ class GD_CORE_API Object {
*
* \return false if the new value cannot be set
*/
virtual bool UpdateProperty(const gd::String& name,
const gd::String& value) {
virtual bool UpdateProperty(const gd::String& name, const gd::String& value) {
return false;
};
///@}
@@ -198,12 +200,12 @@ class GD_CORE_API Object {
std::vector<gd::String> GetAllBehaviorNames() const;
/**
* \brief Return a reference to the behavior called \a name.
* \brief Return a reference to the content of the behavior called \a name.
*/
BehaviorContent& GetBehavior(const gd::String& name);
/**
* \brief Return a reference to the behavior called \a name.
* \brief Return a reference to the content of the behavior called \a name.
*/
const BehaviorContent& GetBehavior(const gd::String& name) const;
@@ -270,6 +272,24 @@ class GD_CORE_API Object {
* object variables
*/
gd::VariablesContainer& GetVariables() { return objectVariables; }
///@}
/**
* \name Effects management
* Member functions related to effects management.
*/
///@{
/**
* \brief Provide access to the gd::EffectsContainer member containing the
* effects.
*/
const gd::EffectsContainer& GetEffects() const { return effectsContainer; }
/**
* \brief Provide access to the gd::EffectsContainer member containing the
* effects.
*/
gd::EffectsContainer& GetEffects() { return effectsContainer; }
///@}
/** \name Serialization
@@ -301,7 +321,9 @@ class GD_CORE_API Object {
///< object.
gd::VariablesContainer
objectVariables; ///< List of the variables of the object
gd::String tags; ///< Comma-separated list of tags
gd::String tags; ///< Comma-separated list of tags
gd::EffectsContainer
effectsContainer; ///< The effects container for the object.
/**
* \brief Derived objects can redefine this method to load custom attributes.

View File

@@ -109,33 +109,6 @@ std::unique_ptr<gd::Object> Project::CreateObject(
return nullptr;
}
gd::Behavior* Project::GetBehavior(const gd::String& type,
const gd::String& platformName) {
for (std::size_t i = 0; i < platforms.size(); ++i) {
if (!platformName.empty() && platforms[i]->GetName() != platformName)
continue;
gd::Behavior* behavior = platforms[i]->GetBehavior(type);
if (behavior) return behavior;
}
return nullptr;
}
gd::BehaviorsSharedData* Project::GetBehaviorSharedDatas(
const gd::String& type, const gd::String& platformName) {
for (std::size_t i = 0; i < platforms.size(); ++i) {
if (!platformName.empty() && platforms[i]->GetName() != platformName)
continue;
gd::BehaviorsSharedData* behaviorSharedData =
platforms[i]->GetBehaviorSharedDatas(type);
if (behaviorSharedData) return behaviorSharedData;
}
return nullptr;
}
#if defined(GD_IDE_ONLY)
std::shared_ptr<gd::BaseEvent> Project::CreateEvent(
const gd::String& type, const gd::String& platformName) {
@@ -515,8 +488,6 @@ void Project::ClearEventsFunctionsExtensions() {
void Project::UnserializeFrom(const SerializerElement& element) {
// Checking version
#if defined(GD_IDE_ONLY)
gd::String updateText;
const SerializerElement& gdVersionElement =
element.GetChild("gdVersion", 0, "GDVersion");
gdMajorVersion =
@@ -528,10 +499,10 @@ void Project::UnserializeFrom(const SerializerElement& element) {
if (gdMajorVersion > gd::VersionWrapper::Major())
gd::LogWarning(
_("The version of GDevelop used to create this game seems to be a new "
"version.\nGDevelop may fail to open the game, or data may be "
"missing.\nYou should check if a new version of GDevelop is "
"available."));
"The version of GDevelop used to create this game seems to be a new "
"version.\nGDevelop may fail to open the game, or data may be "
"missing.\nYou should check if a new version of GDevelop is "
"available.");
else {
if ((gdMajorVersion == gd::VersionWrapper::Major() &&
gdMinorVersion > gd::VersionWrapper::Minor()) ||
@@ -543,22 +514,12 @@ void Project::UnserializeFrom(const SerializerElement& element) {
gdBuildVersion == gd::VersionWrapper::Build() &&
revision > gd::VersionWrapper::Revision())) {
gd::LogWarning(
_("The version of GDevelop used to create this game seems to be "
"greater.\nGDevelop may fail to open the game, or data may be "
"missing.\nYou should check if a new version of GDevelop is "
"available."));
"The version of GDevelop used to create this game seems to be "
"greater.\nGDevelop may fail to open the game, or data may be "
"missing.\nYou should check if a new version of GDevelop is "
"available.");
}
}
// Compatibility code
if (gdMajorVersion <= 1) {
gd::LogError(_(
"The game was saved with version of GDevelop which is too old. Please "
"open and save the game with one of the first version of GDevelop 2. "
"You will then be able to open your game with this GDevelop version."));
return;
}
// End of Compatibility code
#endif
const SerializerElement& propElement =
@@ -682,21 +643,6 @@ void Project::UnserializeFrom(const SerializerElement& element) {
currentPlatform = platforms.back();
#endif
// Compatibility code
#if defined(GD_IDE_ONLY)
if (VersionWrapper::IsOlder(gdMajorVersion, 0, 0, 0, 3, 0, 0, 0)) {
updateText +=
_("Sprite scaling has changed since GD 2: The resizing is made so that "
"the origin point of the object won't move whatever the scale of the "
"object.\n");
updateText +=
_("You may have to slightly change the position of some objects if you "
"have changed their size.\n\n");
updateText += _("Thank you for your understanding.\n");
}
#endif
// End of Compatibility code
#if defined(GD_IDE_ONLY)
GetObjectGroups().UnserializeFrom(
element.GetChild("objectsGroups", 0, "ObjectGroups"));

View File

@@ -390,37 +390,6 @@ class GD_CORE_API Project : public ObjectsContainer {
const gd::String& name,
const gd::String& platformName = "");
/**
* Get the behavior of the given type.
*
* \note A project can use more than one platform. In this case, the first
* platform supporting the behavior is used, unless \a platformName argument
* is not empty.
* It is assumed that each platform provides an equivalent
* behavior.
*
* \param type The type of the behavior
* \param platformName The name of the platform to be used. If empty, the
* first platform supporting the object is used.
*/
gd::Behavior* GetBehavior(const gd::String& type,
const gd::String& platformName = "");
/**
* Get the behavior shared data of the given type.
*
* \note A project can use more than one platform. In this case, the first
* platform supporting the behavior shared data is used, unless \a
* platformName argument is not empty.
* It is assumed that each platform provides equivalent behavior shared data.
*
* \param type The type of behavior
* \param platformName The name of the platform to be used. If empty, the
* first platform supporting the object is used.
*/
gd::BehaviorsSharedData* GetBehaviorSharedDatas(
const gd::String& type, const gd::String& platformName = "");
#if defined(GD_IDE_ONLY)
/**
* Create an event of the given type.

View File

@@ -7,6 +7,7 @@
#include "DummyPlatform.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
@@ -36,31 +37,38 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Identifier") {
SECTION("Object or expression completions when type is string") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", "My"),
gd::ExpressionCompletionDescription::ForExpression("string", "My")};
gd::ExpressionCompletionDescription::ForObject("string", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"string", "My", 0, 2)};
REQUIRE(getCompletionsFor("string", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "My", 2) == expectedEmptyCompletions);
}
SECTION("Object or expression completions when type is number") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number", "My"),
gd::ExpressionCompletionDescription::ForExpression("number", "My")};
gd::ExpressionCompletionDescription::ForObject("number", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"number", "My", 0, 2)};
REQUIRE(getCompletionsFor("number", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "My", 2) == expectedEmptyCompletions);
}
SECTION("Object or expression completions when type is number|string") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number|string", "My"),
gd::ExpressionCompletionDescription::ForExpression("number|string", "My")};
REQUIRE(getCompletionsFor("number|string", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number|string", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("number|string", "My", 2) == expectedEmptyCompletions);
gd::ExpressionCompletionDescription::ForObject(
"number|string", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"number|string", "My", 0, 2)};
REQUIRE(getCompletionsFor("number|string", "My", 0) ==
expectedCompletions);
REQUIRE(getCompletionsFor("number|string", "My", 1) ==
expectedCompletions);
REQUIRE(getCompletionsFor("number|string", "My", 2) ==
expectedEmptyCompletions);
}
SECTION("Object when type is an object") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("object", "My")};
gd::ExpressionCompletionDescription::ForObject("object", "My", 0, 2)};
REQUIRE(getCompletionsFor("object", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("object", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("object", "My", 2) == expectedEmptyCompletions);
@@ -70,7 +78,8 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
// Also test alternate types also considered as objects (but that can
// result in different code generation):
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("objectPtr", "My")};
gd::ExpressionCompletionDescription::ForObject(
"objectPtr", "My", 0, 2)};
REQUIRE(getCompletionsFor("objectPtr", "My", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("objectPtr", "My", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("objectPtr", "My", 2) ==
@@ -78,30 +87,42 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
}
}
SECTION("Operator (number)") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number", ""),
gd::ExpressionCompletionDescription::ForExpression("number", "")};
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedCompletions);
std::vector<gd::ExpressionCompletionDescription> expectedCompletions1{
gd::ExpressionCompletionDescription::ForObject("number", "", 1, 1),
gd::ExpressionCompletionDescription::ForExpression("number", "", 1, 1)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions2{
gd::ExpressionCompletionDescription::ForObject("number", "", 2, 2),
gd::ExpressionCompletionDescription::ForExpression("number", "", 2, 2)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions3{
gd::ExpressionCompletionDescription::ForObject("number", "", 3, 3),
gd::ExpressionCompletionDescription::ForExpression("number", "", 3, 3)};
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedCompletions1);
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedCompletions2);
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedCompletions3);
}
SECTION("Operator (string)") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", ""),
gd::ExpressionCompletionDescription::ForExpression("string", "")};
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) == expectedCompletions);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) == expectedCompletions);
std::vector<gd::ExpressionCompletionDescription> expectedCompletions3{
gd::ExpressionCompletionDescription::ForObject("string", "", 3, 3),
gd::ExpressionCompletionDescription::ForExpression("string", "", 3, 3)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions4{
gd::ExpressionCompletionDescription::ForObject("string", "", 4, 4),
gd::ExpressionCompletionDescription::ForExpression("string", "", 4, 4)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions5{
gd::ExpressionCompletionDescription::ForObject("string", "", 5, 5),
gd::ExpressionCompletionDescription::ForExpression("string", "", 5, 5)};
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) == expectedCompletions3);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) == expectedCompletions4);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) == expectedCompletions5);
}
SECTION("Free function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForExpression("unknown",
"Function")};
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Function", 0, 8)};
std::vector<gd::ExpressionCompletionDescription> expectedExactCompletions{
gd::ExpressionCompletionDescription::ForExpression("unknown",
"Function")
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Function", 0, 8)
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "Function(", 0) ==
expectedCompletions);
@@ -123,33 +144,48 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
expectedEmptyCompletions);
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("unknown", "a"),
gd::ExpressionCompletionDescription::ForExpression("unknown", "a")};
gd::ExpressionCompletionDescription::ForObject("unknown", "a", 9, 10),
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "a", 9, 10)};
REQUIRE(getCompletionsFor("string", "Function(a", 9) ==
expectedCompletions);
}
SECTION("Function with a Variable as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForVariable("scenevar",
"myVar")};
gd::ExpressionCompletionDescription::ForVariable(
"scenevar", "myVar", 33, 38)};
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(myVar",
33) == expectedCompletions);
}
SECTION("Function with a Layer as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForText(
"layer",
gd::MetadataProvider::GetExpressionMetadata(platform,
"MyExtension::MouseX")
.GetParameter(0),
"",
20,
21,
false)};
REQUIRE(getCompletionsFor("number", "MyExtension::MouseX(\"", 20) ==
expectedCompletions);
}
}
SECTION("Partial object or behavior function") {
SECTION("Test with string type") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior("Func",
"MyObject"),
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject")};
"string", "Func", 9, 13, "MyObject")};
REQUIRE(getCompletionsFor("string", "MyObject.Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.Func", 7) ==
@@ -166,14 +202,14 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Test with 'number|string' type") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("number|string",
"MyObject")};
gd::ExpressionCompletionDescription::ForObject(
"number|string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior("Func",
"MyObject"),
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"number|string", "Func", "MyObject")};
"number|string", "Func", 9, 13, "MyObject")};
REQUIRE(getCompletionsFor("number|string", "MyObject.Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("number|string", "MyObject.Func", 7) ==
@@ -193,18 +229,18 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("unknown",
"MyObject")};
gd::ExpressionCompletionDescription::ForObject(
"unknown", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior("Func",
"MyObject"),
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", "MyObject")};
"unknown", "Func", 9, 13, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", "MyObject")
"unknown", "Func", 9, 13, "MyObject")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 0) ==
expectedObjectCompletions);
@@ -229,16 +265,16 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior("MyBehavior",
"MyObject")};
gd::ExpressionCompletionDescription::ForBehavior(
"MyBehavior", 9, 19, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "Func", "MyObject", "MyBehavior")};
"string", "Func", 21, 25, "MyObject", "MyBehavior")};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func", 7) ==
@@ -261,16 +297,16 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Test 2") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("string",
"MyObject")};
gd::ExpressionCompletionDescription::ForObject(
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior("MyBehavior",
"MyObject")};
gd::ExpressionCompletionDescription::ForBehavior(
"MyBehavior", 9, 19, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"string", "", "MyObject", "MyBehavior")};
"string", "", 21, 21, "MyObject", "MyBehavior")};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 0) ==
expectedObjectCompletions);
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::", 7) ==
@@ -292,20 +328,20 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject("unknown",
"MyObject")};
gd::ExpressionCompletionDescription::ForObject(
"unknown", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior("MyBehavior",
"MyObject")};
gd::ExpressionCompletionDescription::ForBehavior(
"MyBehavior", 9, 19, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", "MyObject", "MyBehavior")};
"unknown", "Func", 21, 25, "MyObject", "MyBehavior")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", "MyObject", "MyBehavior")
"unknown", "Func", 21, 25, "MyObject", "MyBehavior")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 0) ==
expectedObjectCompletions);

View File

@@ -24,6 +24,18 @@ bool CheckNodeAtLocationIs(gd::ExpressionParser2& parser,
*node, searchPosition)) != nullptr;
}
template <class TNode>
bool CheckParentNodeAtLocationIs(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
REQUIRE(node != nullptr);
return dynamic_cast<TNode*>(
gd::ExpressionNodeLocationFinder::GetParentNodeAtPosition(
*node, searchPosition)) != nullptr;
}
bool CheckNoNodeAtLocation(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
@@ -34,6 +46,16 @@ bool CheckNoNodeAtLocation(gd::ExpressionParser2& parser,
*node, searchPosition) == nullptr;
}
bool CheckNoParentNodeAtLocation(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
REQUIRE(node != nullptr);
return gd::ExpressionNodeLocationFinder::GetParentNodeAtPosition(
*node, searchPosition) == nullptr;
}
TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
gd::Project project;
gd::Platform platform;
@@ -186,6 +208,13 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "number", "12+\"hello\"", 3) == true);
}
SECTION("Numbers and texts mismatchs (parent node)") {
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 0) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 1) == true);
REQUIRE(CheckNoParentNodeAtLocation(parser, "number", "12+\"hello\"", 2) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 3) == true);
}
SECTION("Valid objects") {
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 0) == true);
@@ -195,6 +224,10 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
parser, "object", "HelloWorld1", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "object", "HelloWorld1", 11) == true);
}
SECTION("Valid objects (parent node)") {
REQUIRE(CheckNoParentNodeAtLocation(
parser, "object", "HelloWorld1", 0) == true);
}
SECTION("Invalid objects") {
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "a+b", 0) == true);
@@ -296,6 +329,29 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
52) == true);
}
SECTION("Parent node") {
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 0) ==
true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 6) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
35) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
35) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
39) == true);
}
}
SECTION("Invalid function calls") {
@@ -316,6 +372,12 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
REQUIRE(CheckNoNodeAtLocation(parser, "number", "Idontexist(12)", 14) ==
true);
}
SECTION("Invalid function calls (parent node)") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "Idontexist(12)", 12) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 12) == true);
}
SECTION("Unterminated function calls") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(

View File

@@ -219,6 +219,12 @@ namespace gdjs {
* @param opacity The new opacity of the object (0-255).
*/
setOpacity(opacity: float): void {
if (opacity < 0) {
opacity = 0;
}
if (opacity > 255) {
opacity = 255;
}
this._opacity = opacity;
this._renderer.updateOpacity();
}

View File

@@ -684,9 +684,9 @@ module.exports = {
}
this._pixiObject.position.x =
this._instance.getX() + this._pixiObject.width / 2;
this._instance.getX() + (this._pixiObject.textWidth * scale) / 2;
this._pixiObject.position.y =
this._instance.getY() + this._pixiObject.height / 2;
this._instance.getY() + (this._pixiObject.textHeight * scale) / 2;
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);

View File

@@ -149,9 +149,8 @@ namespace gdjs {
}
updatePosition(): void {
this._pixiObject.position.x = this._object.x + this._pixiObject.width / 2;
this._pixiObject.position.y =
this._object.y + this._pixiObject.height / 2;
this._pixiObject.position.x = this._object.x + this.getWidth() / 2;
this._pixiObject.position.y = this._object.y + this.getHeight() / 2;
}
updateAngle(): void {
@@ -163,11 +162,11 @@ namespace gdjs {
}
getWidth(): float {
return this._pixiObject.textWidth;
return this._pixiObject.textWidth * this.getScale();
}
getHeight(): float {
return this._pixiObject.height;
return this._pixiObject.textHeight * this.getScale();
}
}
export const BitmapTextRuntimeObjectRenderer = BitmapTextRuntimeObjectPixiRenderer;

View File

@@ -148,6 +148,7 @@ namespace gdjs {
setText(text: string): void {
this._text = text;
this._renderer.updateTextContent();
this.hitBoxesDirty = true;
}
/**
@@ -174,6 +175,7 @@ namespace gdjs {
setScale(scale: float): void {
this._scale = scale;
this._renderer.updateScale();
this.hitBoxesDirty = true;
}
getScale(): float {
@@ -255,6 +257,12 @@ namespace gdjs {
* @param opacity The new opacity of the object (0-255).
*/
setOpacity(opacity: float): void {
if (opacity < 0) {
opacity = 0;
}
if (opacity > 255) {
opacity = 255;
}
this._opacity = opacity;
this._renderer.updateOpacity();
}
@@ -273,6 +281,7 @@ namespace gdjs {
setWrappingWidth(width: float): void {
this._wrappingWidth = width;
this._renderer.updateWrappingWidth();
this.hitBoxesDirty = true;
}
/**
@@ -285,6 +294,7 @@ namespace gdjs {
setWordWrap(wordWrap: boolean): void {
this._wordWrap = wordWrap;
this._renderer.updateWrappingWidth();
this.hitBoxesDirty = true;
}
getWordWrap(): boolean {
@@ -295,14 +305,14 @@ namespace gdjs {
* Get the width of the object.
*/
getWidth(): float {
return this._renderer.getWidth() * this.getScale();
return this._renderer.getWidth();
}
/**
* Get the height of the object.
*/
getHeight(): float {
return this._renderer.getHeight() * this.getScale();
return this._renderer.getHeight();
}
}
gdjs.registerObject(

View File

@@ -785,7 +785,7 @@ namespace gdjs {
variables: gdjs.dialogueTree.runner.variables.data,
visited: gdjs.dialogueTree.runner.visited,
};
gdjs.evtTools.network._objectToVariable(dialogueState, outputVariable);
outputVariable.fromJSObject(dialogueState);
};
/**
@@ -795,9 +795,7 @@ namespace gdjs {
* @param inputVariable The structured variable where to load the State from.
*/
gdjs.dialogueTree.loadState = function (inputVariable: gdjs.Variable) {
const loadedState = JSON.parse(
gdjs.evtTools.network.variableStructureToJSON(inputVariable)
);
const loadedState = inputVariable.toJSObject();
if (!loadedState) {
console.error('Load state variable is empty:', inputVariable);
return;

View File

@@ -39,12 +39,14 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
type: '',
behaviors: [{ name: 'Behavior1', type: 'DraggableBehavior::Draggable' }],
variables: [],
effects: [],
});
var object2 = new gdjs.RuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [{ name: 'Behavior1', type: 'DraggableBehavior::Draggable' }],
variables: [],
effects: [],
});
runtimeScene.addObject(object);
runtimeScene.addObject(object2);

View File

@@ -125,6 +125,12 @@ module.exports = {
.setValue('7')
.setLabel(_('Quality (between 0 and 20)'))
.setType('number');
advancedBloomProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const asciiEffect = extension
.addEffect('Ascii')
@@ -215,8 +221,8 @@ module.exports = {
const blurEffect = extension
.addEffect('Blur')
.setFullName(_('Blur'))
.setDescription(_('Blur the rendered image.'))
.setFullName(_('Blur (Gaussian, slow - prefer to use Kawase blur)'))
.setDescription(_('Blur the rendered image. This is slow, so prefer to use Kawase blur in most cases.'))
.addIncludeFile('Extensions/Effects/blur-pixi-filter.js');
const blurProperties = blurEffect.getProperties();
blurProperties
@@ -412,6 +418,12 @@ module.exports = {
.setLabel(_('Noise Frequency'))
.setType('number')
.setDescription('Number of updates per second (0: no updates)');
crtProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const displacementEffect = extension
.addEffect('Displacement')
@@ -511,6 +523,12 @@ module.exports = {
.setValue('false')
.setLabel(_('Shadow only (shows only the shadow when enabled)'))
.setType('boolean');
dropShadowProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const glitchEffect = extension
.addEffect('Glitch')
@@ -680,12 +698,18 @@ module.exports = {
.setValue('100')
.setLabel(_('Center Y (between -1000 and 100)'))
.setType('number');
godrayProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const kawaseBlurEffect = extension
.addEffect('KawaseBlur')
.setFullName(_('Kawase blur'))
.setFullName(_('Blur (Kawase, fast)'))
.setDescription(
_('A much faster blur than Gaussian blur, but more complicated to use.')
_('Blur the rendered image, with much better performance than Gaussian blur.')
)
.addIncludeFile('Extensions/Effects/pixi-filters/filter-kawase-blur.js')
.addIncludeFile('Extensions/Effects/kawase-blur-pixi-filter.js');
@@ -710,6 +734,12 @@ module.exports = {
.setValue('3')
.setLabel(_('Quality (between 1 and 20)'))
.setType('number');
kawaseBlurProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const lightNightEffect = extension
.addEffect('LightNight')
@@ -839,6 +869,12 @@ module.exports = {
.setValue('1')
.setLabel(_('Color of the outline'))
.setType('color');
outlineProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const pixelateEffect = extension
.addEffect('Pixelate')
@@ -891,6 +927,12 @@ module.exports = {
.setValue('0.5')
.setLabel(_('Center Y (between 0 and 1, 0.5 is image middle)'))
.setType('number');
radialBlurProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
const reflectionEffect = extension
.addEffect('Reflection')
@@ -1061,7 +1103,7 @@ module.exports = {
.setValue('20')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for filter area'));
.setDescription(_('Padding for the visual effect area'));
twistProperties
.getOrCreate('offsetX')
.setValue('0.5')
@@ -1100,6 +1142,12 @@ module.exports = {
.setValue('0.3')
.setLabel(_('strength (between 0 and 5)'))
.setType('number');
zoomBlurProperties
.getOrCreate('padding')
.setValue('0')
.setLabel(_('Padding'))
.setType('number')
.setDescription(_('Padding for the visual effect area'));
return extension;
},

View File

@@ -17,6 +17,8 @@ namespace gdjs {
advancedBloomFilter.blur = value;
} else if (parameterName === 'quality') {
advancedBloomFilter.quality = value;
} else if (parameterName === 'padding') {
advancedBloomFilter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {},

View File

@@ -42,6 +42,8 @@ namespace gdjs {
filter.animationSpeed = value;
} else if (parameterName === 'animationFrequency') {
filter.animationFrequency = value;
} else if (parameterName === 'padding') {
filter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {},

View File

@@ -17,6 +17,8 @@ namespace gdjs {
dropShadowFilter.distance = value;
} else if (parameterName === 'rotation') {
dropShadowFilter.rotation = value;
} else if (parameterName === 'padding') {
dropShadowFilter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {

View File

@@ -26,6 +26,8 @@ namespace gdjs {
filter.y = value;
} else if (parameterName === 'animationSpeed') {
filter.animationSpeed = value;
} else if (parameterName === 'padding') {
filter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {},

View File

@@ -9,6 +9,8 @@ namespace gdjs {
const outlineFilter = (filter as unknown) as PIXI.filters.OutlineFilter;
if (parameterName === 'thickness') {
outlineFilter.thickness = value;
} else if (parameterName === 'padding') {
outlineFilter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {

View File

@@ -33,6 +33,8 @@ namespace gdjs {
} else if (parameterName === 'centerY') {
// @ts-ignore - extra properties are stored on the filter.
radialBlurFilter._centerY = value;
} else if (parameterName === 'padding') {
radialBlurFilter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {},

View File

@@ -31,6 +31,8 @@ namespace gdjs {
0,
20
);
} else if (parameterName === 'padding') {
zoomBlurFilter.padding = value;
}
},
updateStringParameter: function (filter, parameterName, value) {},

View File

@@ -43,10 +43,7 @@ namespace gdjs {
FBInstant.player
.getDataAsync([key])
.then(function (data) {
gdjs.evtTools.network.jsonToVariableStructure(
data[key],
successVariable
);
successVariable.fromJSON(data[key]);
})
.catch(function (error) {
errorVariable.setString(error.message || 'Unknown error');
@@ -64,7 +61,7 @@ namespace gdjs {
errorVariable.setString('');
successVariable.setString('');
const data = {};
data[key] = gdjs.evtTools.network.variableStructureToJSON(variable);
data[key] = JSON.stringify(variable.toJSObject());
FBInstant.player
.setDataAsync(data)
.then(function () {
@@ -86,9 +83,7 @@ namespace gdjs {
}
errorVariable.setString('');
successVariable.setString('');
const data = gdjs.evtTools.network.variableStructureToJSON(
extraDataVariable
);
const data = JSON.stringify(extraDataVariable.toJSObject());
FBInstant.getLeaderboardAsync(leaderboardName)
.then(function (leaderboard) {
return leaderboard.setScoreAsync(score, data);
@@ -123,10 +118,7 @@ namespace gdjs {
scoreVariable.setNumber(
entry.getScore() === null ? -1 : entry.getScore()
);
gdjs.evtTools.network.jsonToVariableStructure(
entry.getExtraData(),
extraDataVariable
);
extraDataVariable.fromJSON(entry.getExtraData());
})
.catch(function (error) {
errorVariable.setString(error.message || 'Unknown error');

View File

@@ -274,13 +274,12 @@ namespace gdjs {
resultVar: gdjs.Variable
) {
const fileSystem = gdjs.fileSystem._getFs();
const network = gdjs.evtTools.network;
let result = 'error';
if (fileSystem && network) {
if (fileSystem) {
try {
fileSystem.writeFileSync(
savePath,
network.variableStructureToJSON(variable),
JSON.stringify(variable.toJSObject()),
'utf8'
);
result = 'ok';
@@ -306,11 +305,10 @@ namespace gdjs {
resultVar: gdjs.Variable
) {
const fileSystem = gdjs.fileSystem._getFs();
const network = gdjs.evtTools.network;
if (fileSystem && network) {
if (fileSystem) {
fileSystem.writeFile(
savePath,
network.variableStructureToJSON(variable),
JSON.stringify(variable.toJSObject()),
'utf8',
(err) => {
resultVar.setString('ok');
@@ -368,13 +366,12 @@ namespace gdjs {
resultVar: gdjs.Variable
) {
const fileSystem = gdjs.fileSystem._getFs();
const network = gdjs.evtTools.network;
let result = 'error';
if (fileSystem && network) {
if (fileSystem) {
try {
const data = fileSystem.readFileSync(loadPath, 'utf8');
if (data) {
network.jsonToVariableStructure(data, variable);
variable.fromJSON(data);
result = 'ok';
}
} catch (err) {
@@ -401,11 +398,10 @@ namespace gdjs {
resultVar: gdjs.Variable
) {
const fileSystem = gdjs.fileSystem._getFs();
const network = gdjs.evtTools.network;
if (fileSystem && network) {
if (fileSystem) {
fileSystem.readFile(loadPath, 'utf8', (err, data) => {
if (data) {
network.jsonToVariableStructure(data, variable);
variable.fromJSON(data);
resultVar.setString('ok');
}
if (err) {

View File

@@ -6,6 +6,241 @@ namespace gdjs {
* @namespace
*/
export namespace firestore {
const queries = new Map<string, firebase.firestore.Query>();
/**
* Converts a firebase document snapshot to a plain dictionary,
* so that it may be serialized or converted to a {@link gdjs.Variable}.
*
* @param doc - The document snapshot.
* @returns - The converted object.
*/
const documentSnapshotToSerializable = (
doc: firebase.firestore.DocumentSnapshot
) => ({
data: doc.data(),
exists: doc.exists,
id: doc.id,
});
/**
* Converts a firebase query snapshot to a plain dictionary,
* so that it may be serialized or converted to a {@link gdjs.Variable}.
*
* @param query - The query snapshot.
* @returns - The converted object.
*/
const querySnapshotToSerializable = (
query: firebase.firestore.QuerySnapshot
) => ({
size: query.size,
empty: query.empty,
docs: query.docs.map(documentSnapshotToSerializable),
});
/**
* Initiate a query over a collection.
* @param queryID - The name of the new query.
* @param collectionName - The name of the collection to query.
*/
export const startQuery = (queryID: string, collectionName: string) => {
queries.set(queryID, firebase.firestore().collection(collectionName));
};
/**
* Create a new query from a base query.
* @param queryID - The name of the new query.
* @param sourceQueryID - The name of the source query.
*/
export const startQueryFrom = (
queryID: string,
sourceQueryID: string
) => {
if (queries.has(sourceQueryID))
queries.set(queryID, queries.get(sourceQueryID)!);
};
/**
* Filters out documents whose fields do not match a condition
* from a query.
* @param queryID - The query to add the filter to.
* @param field - The field to run the condition on.
* @param op - The condtion operator.
* @param value - The value to check against.
*/
export const queryWhere = (
queryID: string,
field: string,
op: Exclude<
firebase.firestore.WhereFilterOp,
// Exclude unsupported "batch" operations (as they require an array as value to check)
'in' | 'array-contains-any' | 'not-in'
>,
value: string | number
) => {
if (queries.has(queryID))
queries.set(queryID, queries.get(queryID)!.where(field, op, value));
};
/**
* Orders the documents in a query.
*
* @param queryID - The query to add the filter to.
* @param field - The field to order by.
* @param direction - The direction of ordering (ascending or descending).
*/
export const queryOrderBy = (
queryID: string,
field: string,
direction: firebase.firestore.OrderByDirection
) => {
if (queries.has(queryID))
queries.set(
queryID,
queries.get(queryID)!.orderBy(field, direction)
);
};
/**
* Limits the amount of documents returned by the query.
*
* @param queryID - The query to add the filter to.
* @param amount - The amount of documents to limit to
* @param last - If true, limits to the last documents instead of the first documents.
*/
export const queryLimit = (
queryID: string,
amount: integer,
last: boolean
) => {
if (queries.has(queryID))
queries.set(
queryID,
queries.get(queryID)![last ? 'limitToLast' : 'limit'](amount)
);
};
/**
* Makes a query skip documents after or before a certain
* value of a field the query was ordered with.
*
* @param queryID - The query to add the filter to.
* @param value - The value of the field ordered by.
* @param before - If set to true, all documents before the document are skipped, else all documents after it are skipped.
* @param includeSelf - If set to true, doesn't skip the document.
*/
export const querySkipSome = (
queryID: string,
value: number,
before: boolean,
includeSelf: boolean
) => {
if (queries.has(queryID))
queries.set(
queryID,
queries
.get(queryID)!
[
before
? includeSelf
? 'endAt'
: 'endBefore'
: includeSelf
? 'startAt'
: 'startAfter'
](value)
);
};
/**
* Execute a query and store results in a callback variable.
*
* @param queryID - The query to execute.
* @param [callbackValueVariable] - The variable where to store the result.
* @param [callbackStatusVariable] - The variable where to store if the operation was successful.
*/
export const executeQuery = (
queryID: string,
callbackValueVariable?: gdjs.Variable,
callbackStatusVariable?: gdjs.Variable
) => {
if (!queries.has(queryID)) return;
queries
.get(queryID)!
.get()
.then((snapshot) => {
if (typeof callbackStatusVariable !== 'undefined')
callbackStatusVariable.setString('ok');
if (typeof callbackValueVariable !== 'undefined')
callbackValueVariable.fromJSObject(
querySnapshotToSerializable(snapshot)
);
})
.catch((error) => {
if (typeof callbackStatusVariable !== 'undefined')
callbackStatusVariable.setString(error.message);
});
};
/**
* Watch a query and store results in a callback
* variable whenever a documents starts/stops
* matching the query or a document matching
* the query is modified.
*
* @param queryID - The query to execute.
* @param [callbackValueVariable] - The variable where to store the result.
* @param [callbackStatusVariable] - The variable where to store if the operation was successful.
*/
export const watchQuery = (
queryID: string,
callbackValueVariable?: gdjs.Variable,
callbackStatusVariable?: gdjs.Variable
) => {
if (!queries.has(queryID)) return;
queries.get(queryID)!.onSnapshot(
(snapshot) => {
if (typeof callbackStatusVariable !== 'undefined')
callbackStatusVariable.setString('ok');
if (typeof callbackValueVariable !== 'undefined')
callbackValueVariable.fromJSObject(
querySnapshotToSerializable(snapshot)
);
},
(error) => {
if (typeof callbackStatusVariable !== 'undefined')
callbackStatusVariable.setString(error.message);
}
);
};
/**
* Adds a variable in a collection as document with a unique name.
* @param collectionName - The collection where to store the variable.
* @param variable - The variable to write.
* @param [callbackStateVariable] - The variable where to store the result.
*/
export const addDocument = (
collectionName: string,
variable: gdjs.Variable,
callbackStateVariable?: gdjs.Variable
) => {
firebase
.firestore()
.collection(collectionName)
.add(replaceTimestampsInObject(variable.toJSObject()))
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
})
.catch((error) => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString(error.message);
});
};
/**
* Writes a variable in a collection as document.
* @param collectionName - The collection where to store the variable.
@@ -23,11 +258,7 @@ namespace gdjs {
.firestore()
.collection(collectionName)
.doc(variableName)
.set(
JSON.parse(
gdjs.evtTools.network.variableStructureToJSON(variable)
)
)
.set(replaceTimestampsInObject(variable.toJSObject()))
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
@@ -59,7 +290,7 @@ namespace gdjs {
.firestore()
.collection(collectionName)
.doc(documentName)
.set({ [field]: value }, { merge: merge })
.set({ [field]: replaceTimestampInString(value) }, { merge: merge })
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
@@ -87,11 +318,7 @@ namespace gdjs {
.firestore()
.collection(collectionName)
.doc(variableName)
.update(
JSON.parse(
gdjs.evtTools.network.variableStructureToJSON(variable)
)
)
.update(replaceTimestampsInObject(variable.toJSObject()))
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
@@ -121,7 +348,7 @@ namespace gdjs {
.firestore()
.collection(collectionName)
.doc(documentName)
.update({ [field]: value })
.update({ [field]: replaceTimestampInString(value) })
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
@@ -209,10 +436,7 @@ namespace gdjs {
callbackStateVariable.setString('ok');
if (callbackValueVariable)
gdjs.evtTools.network._objectToVariable(
doc.data(),
callbackValueVariable
);
callbackValueVariable.fromJSObject(doc.data());
})
.catch((error) => {
if (typeof callbackStateVariable !== 'undefined')
@@ -245,10 +469,7 @@ namespace gdjs {
callbackStateVariable.setString('ok');
if (callbackValueVariable)
gdjs.evtTools.network._objectToVariable(
doc.get(field),
callbackValueVariable
);
callbackValueVariable.fromJSObject(doc.get(field));
})
.catch((error) => {
if (typeof callbackStateVariable !== 'undefined')
@@ -329,6 +550,8 @@ namespace gdjs {
* @param collectionName - The collection where to count documents.
* @param [callbackValueVariable] - The variable where to store the result.
* @param [callbackStateVariable] - The variable where to store if the operation was successful.
*
* @deprecated Use a query without filters instead.
*/
export const listDocuments = (
collectionName: string,
@@ -346,9 +569,8 @@ namespace gdjs {
);
if (callbackValueVariable)
gdjs.evtTools.network._objectToVariable(
snapshot.docs.map((doc) => doc.id),
callbackValueVariable
callbackValueVariable.fromJSObject(
snapshot.docs.map((doc) => doc.id)
);
})
.catch((error) => {
@@ -356,6 +578,28 @@ namespace gdjs {
callbackStateVariable.setString(error.message);
});
};
/**
* Returns a special string replaced by a firebase serverTimestamp field value.
*/
export const getServerTimestamp = () =>
'[{__FIREBASE_SERVERSIDE_TIMESTAMP}]';
const replaceTimestampInString = (str: any) => {
if (str === '[{__FIREBASE_SERVERSIDE_TIMESTAMP}]')
return firebase.firestore.FieldValue.serverTimestamp();
else return str;
};
const replaceTimestampsInObject = (object: object): object => {
for (const i in object) {
const item = object[i];
if (typeof item === 'object') replaceTimestampsInObject(item);
else if (item === '[{__FIREBASE_SERVERSIDE_TIMESTAMP}]')
object[i] = firebase.firestore.FieldValue.serverTimestamp();
}
return object;
};
}
}
}

View File

@@ -20,11 +20,7 @@ namespace gdjs {
firebase
.database()
.ref(path)
.set(
JSON.parse(
gdjs.evtTools.network.variableStructureToJSON(variable)
)
)
.set(variable.toJSObject())
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
@@ -76,11 +72,7 @@ namespace gdjs {
firebase
.database()
.ref(path)
.update(
JSON.parse(
gdjs.evtTools.network.variableStructureToJSON(variable)
)
)
.update(variable.toJSObject())
.then(() => {
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');
@@ -190,10 +182,7 @@ namespace gdjs {
callbackStateVariable.setString('ok');
if (typeof callbackValueVariable !== 'undefined')
gdjs.evtTools.network._objectToVariable(
snapshot.val(),
callbackValueVariable
);
callbackValueVariable.fromJSObject(snapshot.val());
})
.catch((error) => {
if (typeof callbackStateVariable !== 'undefined')
@@ -223,10 +212,7 @@ namespace gdjs {
callbackStateVariable.setString('ok');
if (typeof callbackValueVariable !== 'undefined')
gdjs.evtTools.network._objectToVariable(
snapshot.val()[field],
callbackValueVariable
);
callbackValueVariable.fromJSObject(snapshot.val()[field]);
})
.catch((error) => {
if (typeof callbackStateVariable !== 'undefined')

View File

@@ -24,10 +24,7 @@ namespace gdjs {
.httpsCallable(httpFunctionName)(parameter)
.then((response) => {
if (callbackValueVariable)
gdjs.evtTools.network._objectToVariable(
response.data,
callbackValueVariable
);
callbackValueVariable.fromJSObject(response.data);
if (typeof callbackStateVariable !== 'undefined')
callbackStateVariable.setString('ok');

View File

@@ -18,9 +18,7 @@ namespace gdjs {
* @param variable - A structure defining the default variables.
*/
export const setDefaultConfig = (variable: gdjs.Variable) => {
firebase.remoteConfig().defaultConfig = JSON.parse(
gdjs.evtTools.network.variableStructureToJSON(variable)
);
firebase.remoteConfig().defaultConfig = variable.toJSObject();
};
gdjs.evtTools.firebaseTools.onAppCreated.push(() => {

View File

@@ -1048,6 +1048,339 @@ module.exports = {
/* ====== CLOUD FIRESTORE ====== */
extension
.addStrExpression(
'ServerTimestamp',
_('Get server timestamp'),
_(
'Set a field to the timstamp on the server when the request arrives there'
),
_('Firebase/Cloud Firestore'),
'JsPlatform/Extensions/firebase.png'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName(
'gdjs.evtTools.firebaseTools.firestore.getServerTimestamp'
);
extension
.addAction(
'FirestoreStartQuery',
_('Start a query'),
_(
'Start a query on a collection. ' +
'A query allows to get a filtered and ordered list of documents in a collection.'
),
_('Create a query named _PARAM0_ for collection _PARAM1_'),
_('Firebase/Cloud Firestore/Queries/Initialize'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter('string', _('Collection'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.startQuery');
extension
.addAction(
'FirestoreStartQueryFrom',
_('Start a query from another query'),
_('Start a query with the same collection and filters as another one.'),
_('Create a query named _PARAM0_ from query _PARAM1_'),
_('Firebase/Cloud Firestore/Queries/Initialize'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter('string', _('Source query name'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.startQueryFrom');
const operatorDesc =
'See the [Firebase documentation]' +
'(https://firebase.google.com/docs/firestore/query-data/queries#query_operators) to understand the operators. ' +
"It is important as some [don't work when combined]" +
'(https://firebase.google.com/docs/firestore/query-data/queries#query_limitations).';
extension
.addAction(
'FirestoreQueryWhereNumber',
_('Filter by field value'),
_('Only match the documents that have a field passing a check.'),
_(
'Filter query _PARAM0_ to remove documents whose field _PARAM1_ is not _PARAM2__PARAM3_'
),
_('Firebase/Cloud Firestore/Queries/Filters'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter('string', _('Field to check'), '', false)
.addParameter(
'stringWithSelector',
_('Check type'),
'["<", "<=", "==", "!=", ">=", ">", "array-contains"]',
false
)
.setParameterLongDescription(operatorDesc)
.addParameter('expression', _('Value to check'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.queryWhere');
extension
.addAction(
'FirestoreQueryWhereText',
_('Filter by field text'),
_('Only match the documents that have a field passing a check.'),
_(
'Filter query _PARAM0_ to remove documents whose field _PARAM1_ is not _PARAM2__PARAM3_'
),
_('Firebase/Cloud Firestore/Queries/Filters'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter('string', _('Field to check'), '', false)
.addParameter(
'stringWithSelector',
_('Check type'),
'["<", "<=", "==", "!=", ">=", ">", "array-contains"]',
false
)
.setParameterLongDescription(operatorDesc)
.addParameter('string', _('Text to check'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.queryWhere');
extension
.addAction(
'FirestoreQueryOrderBy',
_('Order by field value'),
_('Orders all documents in the query by a the value of a field.'),
_('Order query _PARAM0_ by field _PARAM1_ (direction: _PARAM2_)'),
_('Firebase/Cloud Firestore/Queries/Filters'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter('string', _('Field to order by'), '', false)
.setParameterLongDescription(
'Note that [some limitations may apply when combined with a where query](https://firebase.google.com/docs/firestore/query-data/order-limit-data#limitations).'
)
.addParameter(
'stringWithSelector',
_('Direction (ascending or descending)'),
'["asc", "desc"]',
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.queryOrderBy');
extension
.addAction(
'FirestoreQueryLimit',
_('Limit amount of documents'),
_(
'Limits the amount of documents returned by the query. Can only be used after an order filter.'
),
_(
'Limit query _PARAM0_ to _PARAM1_ documents (begin from the end: _PARAM2_)'
),
_('Firebase/Cloud Firestore/Queries/Filters'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter('expression', _('Amount to limit by'), '', false)
.addParameter('yesorno', _('Begin from the end'), '', false)
.setDefaultValue('false')
.setParameterLongDescription(
'If yes, the last X documents will be kept, else the first X documents will be kept.'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.queryLimit');
extension
.addAction(
'FirestoreQuerySkipSome',
_('Skip some documents'),
_(
'Removes documents before or after a certain value on the field ordered by in a query. Can only be used after an order filter.'
),
_(
'Skip documents with fields (before: _PARAM2_) value _PARAM1_ in query _PARAM0_ (include documents at that value: _PARAM3_)'
),
_('Firebase/Cloud Firestore/Queries/Filters'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter(
'expression',
_('The value of the field ordered by to skip after'),
'',
false
)
.addParameter('yesorno', _('Skip documents before?'), '', false)
.setParameterLongDescription(
'If yes, the documents with a bigger field value will be kept, else the documents with a smaller field value be kept by the query.'
)
.addParameter(
'yesorno',
_(
'Include documents which field value equals the value to skip after?'
),
'',
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.querySkipSome');
extension
.addAction(
'FirestoreExecuteQuery',
_('Execute a query'),
_('Execute the query and store results in a scene variable.'),
_(
'Execute query _PARAM0_ and store results into _PARAM1_ (store result state in _PARAM2_)'
),
_('Firebase/Cloud Firestore/Queries/Execute'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter(
'scenevar',
_('Callback variable where to load the results'),
'',
true
)
.setParameterLongDescription(
'See the shape of the returned data on [the wiki page](http://wiki.compilgames.net/doku.php/gdevelop5/all-features/firebase/firestore#the_query_result).'
)
.addParameter(
'scenevar',
_('Callback variable with state (ok or error message)'),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.executeQuery');
extension
.addAction(
'FirestoreWatchQuery',
_('Watch a query'),
_(
'Executes the query every time a new documents starts ' +
'or stops matching the query, or a document that matches the query has been changed.'
),
_(
'Watch and automatically execute query _PARAM0_ and store results into _PARAM1_ (store result state in _PARAM2_)'
),
_('Firebase/Cloud Firestore/Queries/Execute'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Query name'), '', false)
.addParameter(
'scenevar',
_('Callback variable where to load the results'),
'',
true
)
.setParameterLongDescription(
'See the shape of the returned data on [the wiki page]().'
)
.addParameter(
'scenevar',
_('Callback variable with state (ok or error message)'),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.watchQuery');
extension
.addAction(
'FirestoreEnablePersistence',
@@ -1152,7 +1485,7 @@ module.exports = {
_(
'Write _PARAM2_ to firestore in document _PARAM1_ of collection _PARAM0_ (store result state in _PARAM3_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Documents'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1176,6 +1509,37 @@ module.exports = {
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.writeDocument');
extension
.addAction(
'FirestoreAddDocument',
_('Add a document to firestore'),
_('Adds a document (variable) to cloud firestore with a unique name.'),
_(
'Add _PARAM1_ to firestore collection _PARAM0_ (store result state in _PARAM2_)'
),
_('Firebase/Cloud Firestore/Documents'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
.addParameter('string', _('Collection'), '', false)
.addParameter('scenevar', _('Variable to write'), '', false)
.addParameter(
'scenevar',
_('Callback variable with state (ok or error)'),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(
'Extensions/Firebase/A_firebasejs/B_firebase-firestore.js'
)
.addIncludeFile('Extensions/Firebase/B_firebasetools/C_firebasetools.js')
.addIncludeFile(
'Extensions/Firebase/B_firebasetools/D_cloudfirestoretools.js'
)
.setFunctionName('gdjs.evtTools.firebaseTools.firestore.addDocument');
extension
.addAction(
'FirestoreWriteField',
@@ -1184,7 +1548,7 @@ module.exports = {
_(
'Write _PARAM3_ to firestore in field _PARAM2_ of document _PARAM1_ in collection _PARAM0_ (store result state in _PARAM4_, Merge: _PARAM5_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Fields'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1218,7 +1582,7 @@ module.exports = {
_(
'Update firestore document _PARAM1_ in collection _PARAM0_ with _PARAM2_ (store result state in _PARAM3_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Documents'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1250,7 +1614,7 @@ module.exports = {
_(
'Update field _PARAM2_ of firestore document _PARAM1_ in collection _PARAM0_ with _PARAM3_ (store result state in _PARAM4_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Fields'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1283,7 +1647,7 @@ module.exports = {
_(
'Delete firestore document _PARAM1_ in collection _PARAM0_ (store result state in _PARAM2_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Documents'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1314,7 +1678,7 @@ module.exports = {
_(
'Delete field _PARAM2_ of firestore document _PARAM1_ in collection _PARAM0_ with (store result state in _PARAM3_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Fields'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1346,7 +1710,7 @@ module.exports = {
_(
'Load firestore document _PARAM1_ from collection _PARAM0_ into _PARAM2_ (store result state in _PARAM3_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Documents'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1383,7 +1747,7 @@ module.exports = {
_(
'Load field _PARAM2_ of firestore document _PARAM1_ in collection _PARAM0_ into _PARAM3_ (store result state in _PARAM4_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Fields'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1423,7 +1787,7 @@ module.exports = {
_(
'Check for existence of _PARAM1_ in collection _PARAM0_ and store result in _PARAM2_ (store result state in _PARAM3_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Documents'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1462,7 +1826,7 @@ module.exports = {
_(
'Check for existence of _PARAM2_ in document _PARAM1_ of collection _PARAM0_ and store result in _PARAM3_ (store result state in _PARAM4_)'
),
_('Firebase/Cloud Firestore'),
_('Firebase/Cloud Firestore/Fields'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
)
@@ -1519,6 +1883,7 @@ module.exports = {
'',
true
)
.setHidden()
.getCodeExtraInformation()
.setIncludeFile('Extensions/Firebase/A_firebasejs/A_firebase-base.js')
.addIncludeFile(

View File

@@ -15,7 +15,7 @@ const firebaseConfig = {
/**
* Turns a callback variable into a promise.
* @param {(callbackVariable: {setString: (result: "ok" | string) => void}, result: gdjs.Variable) => any} executor
* @returns
* @returns {Promise<gdjs.Variable>}
*/
const promisifyCallbackVariables = (executor) =>
new Promise((done, err) => {
@@ -32,11 +32,11 @@ const promisifyCallbackVariables = (executor) =>
});
/** A complex variable using all variables types. */
const variable = new gdjs.Variable();
gdjs.evtTools.network._objectToVariable(
{ my: 'test', object: { with: 'all', types: 2 }, it: ['is', true] },
variable
);
const variable = new gdjs.Variable().fromJSObject({
my: 'test',
object: { with: 'all', types: 2 },
it: ['is', true],
});
// The tests require an internet connection, as a real Firebase instance is used.
const describeIfOnline = navigator.onLine ? describe : describe.skip;
@@ -126,13 +126,9 @@ describeIfOnline('Firebase extension end-to-end tests', function () {
goodbye: 'Goodbye',
});
const updater = new gdjs.Variable();
gdjs.evtTools.network._objectToVariable(
{
goodbye: 'See you later',
},
updater
);
const updater = new gdjs.Variable().fromJSObject({
goodbye: 'See you later',
});
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.database.updateVariable(
@@ -225,173 +221,299 @@ describeIfOnline('Firebase extension end-to-end tests', function () {
});
describe('Cloud Firestore', () => {
it('should write and read back a variable from Firestore', async () => {
await promisifyCallbackVariables((callback) => {
gdjs.evtTools.firebaseTools.firestore.writeDocument(
namespace,
'variable',
variable,
callback
);
});
const fetchedVariable = await promisifyCallbackVariables(
(callback, result) =>
gdjs.evtTools.firebaseTools.firestore.getDocument(
describe('Base read/write operations', () => {
it('should write and read back a variable from Firestore', async () => {
await promisifyCallbackVariables((callback) => {
gdjs.evtTools.firebaseTools.firestore.writeDocument(
namespace,
'variable',
result,
variable,
callback
)
);
);
});
expect(fetchedVariable).to.eql(variable);
});
it('should write and read back a field from Firestore', async function () {
await promisifyCallbackVariables((callback) => {
gdjs.evtTools.firebaseTools.firestore.writeField(
namespace,
'field',
'hello',
1,
callback
const fetchedVariable = await promisifyCallbackVariables(
(callback, result) =>
gdjs.evtTools.firebaseTools.firestore.getDocument(
namespace,
'variable',
result,
callback
)
);
expect(fetchedVariable).to.eql(variable);
});
const fetchedVariable = await promisifyCallbackVariables(
(callback, result) =>
gdjs.evtTools.firebaseTools.firestore.getField(
it('should write and read back a field from Firestore', async function () {
await promisifyCallbackVariables((callback) => {
gdjs.evtTools.firebaseTools.firestore.writeField(
namespace,
'field',
'hello',
result,
1,
callback
);
});
const fetchedVariable = await promisifyCallbackVariables(
(callback, result) =>
gdjs.evtTools.firebaseTools.firestore.getField(
namespace,
'field',
'hello',
result,
callback
)
);
expect(fetchedVariable.getType()).to.be.string('number');
expect(fetchedVariable.getAsNumber()).to.be.equal(1);
});
it('should update a field from Firestore', async function () {
const doc = firebase.firestore().doc(namespace + '/update');
// Populate with data to update
await doc.set({ hello: 'Hello', goodbye: 'Goodbye' });
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.updateField(
namespace,
'update',
'hello',
'Good day',
callback
)
);
);
expect((await doc.get()).data()).to.eql({
hello: 'Good day',
goodbye: 'Goodbye',
});
expect(fetchedVariable.getType()).to.be.string('number');
expect(fetchedVariable.getAsNumber()).to.be.equal(1);
});
it('should update a field from Firestore', async function () {
const doc = firebase.firestore().doc(namespace + '/update');
// Populate with data to update
await doc.set({ hello: 'Hello', goodbye: 'Goodbye' });
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.updateField(
namespace,
'update',
'hello',
'Good day',
callback
)
);
expect((await doc.get()).data()).to.eql({
hello: 'Good day',
goodbye: 'Goodbye',
});
const updater = new gdjs.Variable();
gdjs.evtTools.network._objectToVariable(
{
const updater = new gdjs.Variable().fromJSObject({
goodbye: 'See you later',
},
updater
);
});
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.updateDocument(
namespace,
'update',
updater,
callback
)
);
expect((await doc.get()).data()).to.eql({
hello: 'Good day',
goodbye: 'See you later',
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.updateDocument(
namespace,
'update',
updater,
callback
)
);
expect((await doc.get()).data()).to.eql({
hello: 'Good day',
goodbye: 'See you later',
});
});
it('should delete and detect presence of a field from Firestore', async function () {
const doc = firebase.firestore().doc(namespace + '/delete');
// Populate with data to delete
await doc.set({ hello: 'Hello', goodbye: 'Goodbye' });
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasDocument(
namespace,
'delete',
result,
callback
)
)
).getValue()
).to.be.ok();
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasField(
namespace,
'delete',
'hello',
result,
callback
)
)
).getValue()
).to.be.ok();
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.deleteField(
namespace,
'delete',
'hello',
callback
)
);
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasField(
namespace,
'delete',
'hello',
result,
callback
)
)
).getValue()
).to.not.be.ok();
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.deleteDocument(
namespace,
'delete',
callback
)
);
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasDocument(
namespace,
'delete',
result,
callback
)
)
).getValue()
).to.not.be.ok();
});
it('should be able to use server timestamps', async () => {
await promisifyCallbackVariables((callback) => {
gdjs.evtTools.firebaseTools.firestore.writeField(
namespace,
'field',
'timestamp',
gdjs.evtTools.firebaseTools.firestore.getServerTimestamp(),
callback
);
});
expect(
(
await firebase
.firestore()
.doc(namespace + '/field')
.get()
).get('timestamp', { serverTimestamps: 'estimate' })
).to.be.an(firebase.firestore.Timestamp);
});
});
it('should delete and detect presence of a field from Firestore', async function () {
const doc = firebase.firestore().doc(namespace + '/delete');
describe('Firestore Queries', async () => {
it('can get a list of documents from the database', async () => {
const subcollection = namespace + '/collection/sub';
await firebase
.firestore()
.doc(subcollection + '/doc1')
.set({ index: 2, val: 2 });
await firebase
.firestore()
.doc(subcollection + '/doc2')
.set({ index: 1, val: 2 });
await firebase
.firestore()
.doc(subcollection + '/doc3')
.set({ index: 3, val: 1 });
// Populate with data to delete
await doc.set({ hello: 'Hello', goodbye: 'Goodbye' });
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasDocument(
namespace,
'delete',
result,
callback
const executeQuery = async (query) =>
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.executeQuery(
query,
result,
callback
)
)
)
).getValue()
).to.be.ok();
).toJSObject();
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasField(
namespace,
'delete',
'hello',
result,
callback
)
)
).getValue()
).to.be.ok();
// Empty query
gdjs.evtTools.firebaseTools.firestore.startQuery('main', subcollection);
expect(await executeQuery('main')).to.eql({
size: 3,
empty: false,
docs: [
{ id: 'doc1', data: { index: 2, val: 2 }, exists: true },
{ id: 'doc2', data: { index: 1, val: 2 }, exists: true },
{ id: 'doc3', data: { index: 3, val: 1 }, exists: true },
],
});
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.deleteField(
namespace,
'delete',
'hello',
callback
)
);
// Query with selector fitler
gdjs.evtTools.firebaseTools.firestore.startQueryFrom(
'selector',
'main'
);
gdjs.evtTools.firebaseTools.firestore.queryWhere(
'selector',
'index',
'>',
1
);
expect(await executeQuery('selector')).to.eql({
size: 2,
empty: false,
docs: [
{ id: 'doc1', data: { index: 2, val: 2 }, exists: true },
{ id: 'doc3', data: { index: 3, val: 1 }, exists: true },
],
});
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasField(
namespace,
'delete',
'hello',
result,
callback
)
)
).getValue()
).to.not.be.ok();
// Query with order filter
gdjs.evtTools.firebaseTools.firestore.startQueryFrom('order', 'main');
gdjs.evtTools.firebaseTools.firestore.queryOrderBy(
'order',
'index',
'desc'
);
expect(await executeQuery('order')).to.eql({
size: 3,
empty: false,
docs: [
{ id: 'doc3', data: { index: 3, val: 1 }, exists: true },
{ id: 'doc1', data: { index: 2, val: 2 }, exists: true },
{ id: 'doc2', data: { index: 1, val: 2 }, exists: true },
],
});
await promisifyCallbackVariables((callback) =>
gdjs.evtTools.firebaseTools.firestore.deleteDocument(
namespace,
'delete',
callback
)
);
// Query with limit filter
gdjs.evtTools.firebaseTools.firestore.startQueryFrom('limit', 'order');
gdjs.evtTools.firebaseTools.firestore.queryLimit('limit', 2, true);
expect(await executeQuery('limit')).to.eql({
size: 2,
empty: false,
docs: [
{ id: 'doc1', data: { index: 2, val: 2 }, exists: true },
{ id: 'doc2', data: { index: 1, val: 2 }, exists: true },
],
});
expect(
(
await promisifyCallbackVariables((callback, result) =>
gdjs.evtTools.firebaseTools.firestore.hasDocument(
namespace,
'delete',
result,
callback
)
)
).getValue()
).to.not.be.ok();
// Query with skip some filter
gdjs.evtTools.firebaseTools.firestore.startQueryFrom('skip', 'order');
gdjs.evtTools.firebaseTools.firestore.querySkipSome(
'skip',
2,
true,
true
);
expect(await executeQuery('skip')).to.eql({
size: 2,
empty: false,
docs: [
{ id: 'doc3', data: { index: 3, val: 1 }, exists: true },
{ id: 'doc1', data: { index: 2, val: 2 }, exists: true },
],
});
});
});
// Delete the temporary namespace to not bloat the DB

View File

@@ -231,6 +231,41 @@ namespace gdjs {
});
};
/**
* Disconnects from another p2p client.
* @param id - The other client's ID.
*/
export const disconnectFromPeer = (id: string) => {
if (connections[id]) connections[id].close();
};
/**
* Disconnects from all other p2p clients.
*/
export const disconnectFromAllPeers = () => {
for (const connection of Object.values(connections)) connection.close();
};
/**
* Disconnects from all peers and the broker server.
*/
export const disconnectFromAll = () => {
if (peer) {
peer.destroy();
peer = null;
}
};
/**
* Disconnects from the broker server, leaving the connections intact.
*/
export const disconnectFromBroker = () => {
if (peer) {
peer.disconnect();
peer = null;
}
};
/**
* Returns true when the event got triggered by another p2p client.
* @param defaultDataLoss Is data loss allowed (accelerates event handling when true)?
@@ -283,11 +318,7 @@ namespace gdjs {
eventName: string,
variable: gdjs.Variable
) => {
sendDataTo(
id,
eventName,
gdjs.evtTools.network.variableStructureToJSON(variable)
);
sendDataTo(id, eventName, JSON.stringify(variable.toJSObject()));
};
/**
@@ -299,10 +330,7 @@ namespace gdjs {
eventName: string,
variable: gdjs.Variable
) => {
sendDataToAll(
eventName,
gdjs.evtTools.network.variableStructureToJSON(variable)
);
sendDataToAll(eventName, JSON.stringify(variable.toJSObject()));
};
/**
@@ -329,10 +357,7 @@ namespace gdjs {
eventName: string,
variable: gdjs.Variable
) => {
gdjs.evtTools.network.jsonToVariableStructure(
getEventData(eventName),
variable
);
variable.fromJSON(getEventData(eventName));
};
/**

View File

@@ -299,6 +299,69 @@ module.exports = {
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getEventVariable');
extension
.addAction(
'DisconnectFromPeer',
_('Disconnect from a peer'),
_('Disconnects this client from another client.'),
_('Disconnect from client _PARAM0_'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', 'ID of the peer to disconnect from', '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.disconnectFromPeer');
extension
.addAction(
'DisconnectFromAllPeers',
_('Disconnect from all peers'),
_('Disconnects this client from all other clients.'),
_('Disconnect from all clients'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.disconnectFromAllPeers');
extension
.addAction(
'DisconnectFromBroker',
_('Disconnect from broker'),
_('Disconnects the client from the broker server.'),
_('Disconnect the client from the broker'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.disconnectFromBroker');
extension
.addAction(
'DisconnectFromAll',
_('Disconnect from all'),
_(
'Disconnects the client from the broker server and all other clients.'
),
_('Disconnect the client from the broker and other clients'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.disconnectFromAll');
extension
.addStrExpression(
'GetEventData',
@@ -319,9 +382,7 @@ module.exports = {
.addStrExpression(
'GetEventSender',
_('Get event sender'),
_(
'Returns the id of the peer that triggered the event'
),
_('Returns the id of the peer that triggered the event'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)

View File

@@ -134,9 +134,9 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
obj.AddAction("Angle",
_("Angle"),
_("Modify the angle of a Panel Sprite."),
_("the angle"),
"Angle",
"Modify the angle of a Panel Sprite.",
"the angle",
_("Size and angle"),
"res/actions/rotate24.png",
"res/actions/rotate.png")
@@ -149,9 +149,9 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
obj.AddCondition("Angle",
_("Angle"),
_("Check the angle of a Panel Sprite."),
_("the angle"),
"Angle",
"Check the angle of a Panel Sprite.",
"the angle",
_("Size and angle"),
"res/conditions/rotate24.png",
"res/conditions/rotate.png")

View File

@@ -45,7 +45,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Move to a position"),
_("Move the object to a position"),
_("Move _PARAM0_ to _PARAM3_;_PARAM4_"),
"",
"Movement on the path",
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
@@ -61,7 +61,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Path found"),
_("Check if a path has been found."),
_("A path has been found for _PARAM0_"),
"",
"Movement on the path",
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -74,7 +74,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Destination reached"),
_("Check if the destination was reached."),
_("_PARAM0_ reached its destination"),
"",
"Movement on the path",
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -145,7 +145,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Change the acceleration when moving the object"),
_("the acceleration on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -160,7 +160,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Compare the acceleration when moving the object"),
_("the acceleration"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -174,7 +174,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum speed"),
_("Change the maximum speed when moving the object"),
_("the max. speed on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -189,7 +189,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum speed"),
_("Compare the maximum speed when moving the object"),
_("the max. speed"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -203,7 +203,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Speed"),
_("Change the speed of the object on the path"),
_("the speed on the path"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -215,10 +215,10 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("Speed",
_("Speed"),
_("Compare the speed of the object on the path"),
_("Speed on its path"),
_("Compare the speed of the object on its path."),
_("the speed"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -228,11 +228,25 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("GetSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddScopedCondition("MovementAngleIsAround",
_("Angle of movement on its path"),
_("Compare the angle of movement of an object on its path."),
_("Angle of movement of _PARAM0_ is _PARAM2_ (tolerance"
": _PARAM3_ degrees)"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Tolerance, in degrees"));
aut.AddAction("AngularMaxSpeed",
_("Angular maximum speed"),
_("Change the maximum angular speed when moving the object"),
_("the max. angular speed on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -248,7 +262,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Angular maximum speed"),
_("Compare the maximum angular speed when moving the object"),
_("the max. angular speed"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -263,7 +277,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotation offset"),
_("Change the rotation offset applied when moving the object"),
_("the rotation offset on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -278,7 +292,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotation offset"),
_("Compare the rotation offset when moving the object"),
_("the rotation offset"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -294,7 +308,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the size of the extra border applied to the object when "
"planning a path"),
_("the size of the extra border on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -310,7 +324,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the size of the extra border applied to the "
"object when planning a path"),
_("the size of the extra border on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -325,7 +339,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Diagonal movement"),
_("Allow or restrict diagonal movement on the path"),
_("Allow diagonal movement for _PARAM0_ on the path: _PARAM2_"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -340,7 +354,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if the object is allowed to move "
"diagonally on the path"),
_("Diagonal moves allowed for _PARAM0_"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -353,7 +367,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotate the object"),
_("Enable or disable rotation of the object on the path"),
_("Enable rotation of _PARAM0_ on the path: _PARAM2_"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -368,7 +382,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if the object is rotated when traveling on "
"its path."),
_("_PARAM0_ is rotated when traveling on its path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
@@ -380,7 +394,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("GetNodeX",
_("Get a waypoint X position"),
_("Get next waypoint X position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -391,7 +405,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("GetNodeY",
_("Get a waypoint Y position"),
_("Get next waypoint Y position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -402,7 +416,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("NextNodeIndex",
_("Index of the next waypoint"),
_("Get the index of the next waypoint to reach"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -412,7 +426,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("NodeCount",
_("Waypoint count"),
_("Get the number of waypoints on the path"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -422,7 +436,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("NextNodeX",
_("Get next waypoint X position"),
_("Get next waypoint X position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -432,7 +446,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("NextNodeY",
_("Get next waypoint Y position"),
_("Get next waypoint Y position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -442,7 +456,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("LastNodeX",
_("Last waypoint X position"),
_("Last waypoint X position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -452,7 +466,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("LastNodeY",
_("Last waypoint Y position"),
_("Last waypoint Y position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -462,7 +476,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("DestinationX",
_("Destination X position"),
_("Destination X position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -472,7 +486,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("DestinationY",
_("Destination Y position"),
_("Destination Y position"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -482,7 +496,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("Acceleration",
_("Acceleration"),
_("Acceleration of the object on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -492,7 +506,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("MaxSpeed",
_("Maximum speed"),
_("Maximum speed of the object on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -502,7 +516,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("Speed",
_("Speed"),
_("Speed of the object on the path"),
_("Path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -512,7 +526,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("AngularMaxSpeed",
_("Angular maximum speed"),
_("Angular maximum speed of the object on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -522,7 +536,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("AngleOffset",
_("Rotation offset"),
_("Rotation offset applied the object on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -532,7 +546,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("ExtraBorder",
_("Extra border size"),
_("Extra border applied the object on the path"),
_("Path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -559,6 +573,36 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("GetCellHeight")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("MovementAngle",
_("Angle of movement on its path"),
_("Angle of movement on its path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior");
aut.AddExpressionAndConditionAndAction("number",
"GridOffsetX",
_("Grid X offset"),
_("X offset of the virtual grid"),
_("X offset of the virtual grid"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardParameters("number");
aut.AddExpressionAndConditionAndAction("number",
"GridOffsetY",
_("Grid Y offset"),
_("Y offset of the virtual grid"),
_("Y offset of the virtual grid"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardParameters("number");
#endif
}
{

View File

@@ -55,6 +55,16 @@ class PathfindingBehaviorJsExtension : public gd::PlatformExtension {
.SetGetter("getCellHeight");
autConditions["PathfindingBehavior::CellHeight"].SetFunctionName(
"getCellHeight");
autActions["PathfindingBehavior::PathfindingBehavior::SetGridOffsetX"]
.SetFunctionName("setGridOffsetX")
.SetGetter("getGridOffsetX");
autConditions["PathfindingBehavior::PathfindingBehavior::GridOffsetX"].SetFunctionName(
"getGridOffsetX");
autActions["PathfindingBehavior::PathfindingBehavior::SetGridOffsetY"]
.SetFunctionName("setGridOffsetY")
.SetGetter("getGridOffsetY");
autConditions["PathfindingBehavior::PathfindingBehavior::GridOffsetY"].SetFunctionName(
"getGridOffsetY");
autActions["PathfindingBehavior::Acceleration"]
.SetFunctionName("setAcceleration")
.SetGetter("getAcceleration");
@@ -69,6 +79,8 @@ class PathfindingBehaviorJsExtension : public gd::PlatformExtension {
.SetFunctionName("setSpeed")
.SetGetter("getSpeed");
autConditions["PathfindingBehavior::Speed"].SetFunctionName("getSpeed");
autConditions["PathfindingBehavior::PathfindingBehavior::MovementAngleIsAround"]
.SetFunctionName("movementAngleIsAround");
autActions["PathfindingBehavior::AngularMaxSpeed"]
.SetFunctionName("setAngularMaxSpeed")
.SetGetter("getAngularMaxSpeed");
@@ -108,11 +120,14 @@ class PathfindingBehaviorJsExtension : public gd::PlatformExtension {
autExpressions["Acceleration"].SetFunctionName("getAcceleration");
autExpressions["MaxSpeed"].SetFunctionName("getMaxSpeed");
autExpressions["Speed"].SetFunctionName("getSpeed");
autExpressions["MovementAngle"].SetFunctionName("getMovementAngle");
autExpressions["AngularMaxSpeed"].SetFunctionName("getAngularMaxSpeed");
autExpressions["AngleOffset"].SetFunctionName("getAngleOffset");
autExpressions["ExtraBorder"].SetFunctionName("getExtraBorder");
autExpressions["CellWidth"].SetFunctionName("getCellWidth");
autExpressions["CellHeight"].SetFunctionName("getCellHeight");
autExpressions["GridOffsetX"].SetFunctionName("getGridOffsetX");
autExpressions["GridOffsetY"].SetFunctionName("getGridOffsetY");
}
GetBehaviorMetadata("PathfindingBehavior::PathfindingObstacleBehavior")

View File

@@ -21,6 +21,8 @@ void PathfindingBehavior::InitializeContent(
behaviorContent.SetAttribute("angleOffset", 0);
behaviorContent.SetAttribute("cellWidth", 20);
behaviorContent.SetAttribute("cellHeight", 20);
behaviorContent.SetAttribute("gridOffsetX", 0);
behaviorContent.SetAttribute("gridOffsetY", 0);
behaviorContent.SetAttribute("extraBorder", 0);
}
@@ -46,9 +48,13 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
properties[_("Angle offset")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
properties[_("Virtual cell width")].SetValue(
gd::String::From(behaviorContent.GetIntAttribute("cellWidth", 0)));
gd::String::From(behaviorContent.GetDoubleAttribute("cellWidth", 0)));
properties[_("Virtual cell height")].SetValue(
gd::String::From(behaviorContent.GetIntAttribute("cellHeight", 0)));
gd::String::From(behaviorContent.GetDoubleAttribute("cellHeight", 0)));
properties[_("Virtual grid X offset")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetX", 0)));
properties[_("Virtual grid Y offset")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetY", 0)));
properties[_("Extra border size")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("extraBorder")));
@@ -82,9 +88,13 @@ bool PathfindingBehavior::UpdateProperty(gd::SerializerElement& behaviorContent,
else if (name == _("Angle offset"))
behaviorContent.SetAttribute("angleOffset", value.To<float>());
else if (name == _("Virtual cell width"))
behaviorContent.SetAttribute("cellWidth", (int)value.To<unsigned int>());
behaviorContent.SetAttribute("cellWidth", value.To<float>());
else if (name == _("Virtual cell height"))
behaviorContent.SetAttribute("cellHeight", (int)value.To<unsigned int>());
behaviorContent.SetAttribute("cellHeight", value.To<float>());
else if (name == _("Virtual grid X offset"))
behaviorContent.SetAttribute("gridOffsetX", value.To<float>());
else if (name == _("Virtual grid Y offset"))
behaviorContent.SetAttribute("gridOffsetY", value.To<float>());
else
return false;

View File

@@ -17,8 +17,10 @@ namespace gdjs {
_angularMaxSpeed: float;
_rotateObject: boolean;
_angleOffset: float;
_cellWidth: integer;
_cellHeight: integer;
_cellWidth: float;
_cellHeight: float;
_gridOffsetX: float;
_gridOffsetY: float;
_extraBorder: float;
//Attributes used for traveling on the path:
@@ -32,6 +34,8 @@ namespace gdjs {
_manager: PathfindingObstaclesManager;
_searchContext: PathfindingRuntimeBehavior.SearchContext;
_movementAngle: float = 0;
constructor(
runtimeScene: gdjs.RuntimeScene,
behaviorData,
@@ -52,6 +56,8 @@ namespace gdjs {
this._angleOffset = behaviorData.angleOffset;
this._cellWidth = behaviorData.cellWidth;
this._cellHeight = behaviorData.cellHeight;
this._gridOffsetX = behaviorData.gridOffsetX || 0;
this._gridOffsetY = behaviorData.gridOffsetY || 0;
this._extraBorder = behaviorData.extraBorder;
this._manager = gdjs.PathfindingObstaclesManager.getManager(runtimeScene);
this._searchContext = new gdjs.PathfindingRuntimeBehavior.SearchContext(
@@ -84,28 +90,50 @@ namespace gdjs {
if (oldBehaviorData.cellHeight !== newBehaviorData.cellHeight) {
this.setCellHeight(newBehaviorData.cellHeight);
}
if (oldBehaviorData.gridOffsetX !== newBehaviorData.gridOffsetX) {
this._gridOffsetX = newBehaviorData.gridOffsetX;
}
if (oldBehaviorData.gridOffsetY !== newBehaviorData.gridOffsetY) {
this._gridOffsetY = newBehaviorData.gridOffsetY;
}
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
this.setExtraBorder(newBehaviorData.extraBorder);
}
return true;
}
setCellWidth(width: integer): void {
setCellWidth(width: float): void {
this._cellWidth = width;
}
getCellWidth(): integer {
getCellWidth(): float {
return this._cellWidth;
}
setCellHeight(height: integer): void {
setCellHeight(height: float): void {
this._cellHeight = height;
}
getCellHeight(): integer {
getCellHeight(): float {
return this._cellHeight;
}
setGridOffsetX(gridOffsetX: float): void {
this._gridOffsetX = gridOffsetX;
}
getGridOffsetX(): float {
return this._gridOffsetX;
}
setGridOffsetY(gridOffsetY: float): void {
this._gridOffsetY = gridOffsetY;
}
getGridOffsetY(): float {
return this._gridOffsetY;
}
setAcceleration(acceleration: float): void {
this._acceleration = acceleration;
}
@@ -130,6 +158,19 @@ namespace gdjs {
return this._speed;
}
getMovementAngle() {
return this._movementAngle;
}
movementAngleIsAround(degreeAngle: float, tolerance: float) {
return (
gdjs.evtTools.common.angleDifference(
this._movementAngle,
degreeAngle
) <= tolerance
);
}
setAngularMaxSpeed(angularMaxSpeed: float): void {
this._angularMaxSpeed = angularMaxSpeed;
}
@@ -275,10 +316,16 @@ namespace gdjs {
const owner = this.owner;
//First be sure that there is a path to compute.
const targetCellX = Math.round(x / this._cellWidth);
const targetCellY = Math.round(y / this._cellHeight);
const startCellX = Math.round(owner.getX() / this._cellWidth);
const startCellY = Math.round(owner.getY() / this._cellHeight);
const targetCellX = Math.round((x - this._gridOffsetX) / this._cellWidth);
const targetCellY = Math.round(
(y - this._gridOffsetY) / this._cellHeight
);
const startCellX = Math.round(
(owner.getX() - this._gridOffsetX) / this._cellWidth
);
const startCellY = Math.round(
(owner.getY() - this._gridOffsetY) / this._cellHeight
);
if (startCellX == targetCellX && startCellY == targetCellY) {
this._path.length = 0;
this._path.push([owner.getX(), owner.getY()]);
@@ -292,6 +339,7 @@ namespace gdjs {
this._searchContext.allowDiagonals(this._allowDiagonals);
this._searchContext.setObstacles(this._manager);
this._searchContext.setCellSize(this._cellWidth, this._cellHeight);
this._searchContext.setGridOffset(this._gridOffsetX, this._gridOffsetY);
this._searchContext.setStartPosition(owner.getX(), owner.getY());
this._searchContext.setObjectSize(
owner.getX() - owner.getDrawableX() + this._extraBorder,
@@ -311,8 +359,10 @@ namespace gdjs {
if (finalPathLength === this._path.length) {
this._path.push([0, 0]);
}
this._path[finalPathLength][0] = node.pos[0] * this._cellWidth;
this._path[finalPathLength][1] = node.pos[1] * this._cellHeight;
this._path[finalPathLength][0] =
node.pos[0] * this._cellWidth + this._gridOffsetX;
this._path[finalPathLength][1] =
node.pos[1] * this._cellHeight + this._gridOffsetY;
node = node.parent;
finalPathLength++;
}
@@ -344,6 +394,8 @@ namespace gdjs {
this._totalSegmentTime = Math.sqrt(pathX * pathX + pathY * pathY);
this._timeOnSegment = 0;
this._reachedEnd = false;
this._movementAngle =
((Math.atan2(pathY, pathX) * 180) / Math.PI + 360) % 360;
} else {
this._reachedEnd = true;
this._speed = 0;
@@ -446,11 +498,11 @@ namespace gdjs {
parent: Node | null = null;
open: boolean = true;
constructor(xPos: float, yPos: float) {
constructor(xPos: integer, yPos: integer) {
this.pos = [xPos, yPos];
}
reinitialize(xPos: float, yPos: float) {
reinitialize(xPos: integer, yPos: integer) {
this.pos[0] = xPos;
this.pos[1] = yPos;
this.cost = 0;
@@ -477,28 +529,28 @@ namespace gdjs {
_maxComplexityFactor: integer = 50;
_cellWidth: float = 20;
_cellHeight: float = 20;
_gridOffsetX: float = 0;
_gridOffsetY: float = 0;
_leftBorder: integer = 0;
_rightBorder: integer = 0;
_topBorder: integer = 0;
_bottomBorder: integer = 0;
_distanceFunction: (pt1: FloatPoint, pt2: FloatPoint) => float;
_allNodes: Node[][] = [];
//An array of array. Nodes are indexed by their x position, and then by their y position.
_allNodes: Node[][] = [];
//An array of nodes sorted by their estimate cost (First node = Lower estimate cost).
_openNodes: Node[] = [];
_closeObstacles: PathfindingObstacleRuntimeBehavior[] = [];
//Used by getNodes to temporarily store obstacles near a position.
_closeObstacles: PathfindingObstacleRuntimeBehavior[] = [];
//Old nodes constructed in a previous search are stored here to avoid temporary objects (see _freeAllNodes method).
_nodeCache: Node[] = [];
constructor(obstacles: PathfindingObstaclesManager) {
this._obstacles = obstacles;
this._distanceFunction = PathfindingRuntimeBehavior.euclideanDistance;
//An array of nodes sorted by their estimate cost (First node = Lower estimate cost).
}
//Old nodes constructed in a previous search are stored here to avoid temporary objects (see _freeAllNodes method).
setObstacles(
obstacles: PathfindingObstaclesManager
): PathfindingRuntimeBehavior.SearchContext {
@@ -549,6 +601,15 @@ namespace gdjs {
return this;
}
setGridOffset(
gridOffsetX: float,
gridOffsetY: float
): PathfindingRuntimeBehavior.SearchContext {
this._gridOffsetX = gridOffsetX;
this._gridOffsetY = gridOffsetY;
return this;
}
computePathTo(targetX: float, targetY: float) {
if (this._obstacles === null) {
console.log(
@@ -556,10 +617,18 @@ namespace gdjs {
);
return;
}
this._destination[0] = Math.round(targetX / this._cellWidth);
this._destination[1] = Math.round(targetY / this._cellHeight);
this._start[0] = Math.round(this._startX / this._cellWidth);
this._start[1] = Math.round(this._startY / this._cellHeight);
this._destination[0] = Math.round(
(targetX - this._gridOffsetX) / this._cellWidth
);
this._destination[1] = Math.round(
(targetY - this._gridOffsetY) / this._cellHeight
);
this._start[0] = Math.round(
(this._startX - this._gridOffsetX) / this._cellWidth
);
this._start[1] = Math.round(
(this._startY - this._gridOffsetY) / this._cellHeight
);
//Initialize the algorithm
this._freeAllNodes();
@@ -679,7 +748,7 @@ namespace gdjs {
* *All* nodes should be created using this method: The cost of the node is computed thanks
* to the objects flagged as obstacles.
*/
_getNode(xPos: float, yPos: float): Node {
_getNode(xPos: integer, yPos: integer): Node {
//First check if their is a node a the specified position.
if (this._allNodes.hasOwnProperty(xPos)) {
if (this._allNodes[xPos].hasOwnProperty(yPos)) {
@@ -698,6 +767,9 @@ namespace gdjs {
newNode = new Node(xPos, yPos);
}
const nodeCenterX = xPos * this._cellWidth + this._gridOffsetX;
const nodeCenterY = yPos * this._cellHeight + this._gridOffsetY;
//...and update its cost according to obstacles
let objectsOnCell = false;
const radius =
@@ -705,25 +777,33 @@ namespace gdjs {
? this._cellHeight * 2
: this._cellWidth * 2;
this._obstacles.getAllObstaclesAround(
xPos * this._cellWidth,
yPos * this._cellHeight,
nodeCenterX,
nodeCenterY,
radius,
this._closeObstacles
);
for (let k = 0; k < this._closeObstacles.length; ++k) {
const obj = this._closeObstacles[k].owner;
const topLeftCellX = Math.floor(
(obj.getDrawableX() - this._rightBorder) / this._cellWidth
(obj.getDrawableX() - this._rightBorder - this._gridOffsetX) /
this._cellWidth
);
const topLeftCellY = Math.floor(
(obj.getDrawableY() - this._bottomBorder) / this._cellHeight
(obj.getDrawableY() - this._bottomBorder - this._gridOffsetY) /
this._cellHeight
);
const bottomRightCellX = Math.ceil(
(obj.getDrawableX() + obj.getWidth() + this._leftBorder) /
(obj.getDrawableX() +
obj.getWidth() +
this._leftBorder -
this._gridOffsetX) /
this._cellWidth
);
const bottomRightCellY = Math.ceil(
(obj.getDrawableY() + obj.getHeight() + this._topBorder) /
(obj.getDrawableY() +
obj.getHeight() +
this._topBorder -
this._gridOffsetY) /
this._cellHeight
);
if (
@@ -733,14 +813,12 @@ namespace gdjs {
yPos < bottomRightCellY
) {
objectsOnCell = true;
//The cell is impassable, stop here.
//Superimpose obstacles
if (this._closeObstacles[k].isImpassable()) {
//The cell is impassable, stop here.
newNode.cost = -1;
break;
} else {
//Superimpose obstacles
newNode.cost += this._closeObstacles[k].getCost();
}
}
@@ -758,8 +836,8 @@ namespace gdjs {
* Add a node to the openNodes (only if the cost to reach it is less than the existing cost, if any).
*/
_addOrUpdateNode(
newNodeX: float,
newNodeY: float,
newNodeX: integer,
newNodeY: integer,
currentNode: Node,
factor: float
) {

View File

@@ -290,7 +290,7 @@ namespace gdjs {
//Colliding: Try to push out from the solid.
//Note that jump thru are never obstacle on X axis.
while (
this._isCollidingWith(
this._isCollidingWithOneOf(
this._potentialCollidingObjects,
floorPlatformId,
/*excludeJumpthrus=*/
@@ -312,7 +312,7 @@ namespace gdjs {
if (this._state === this._onFloor) {
object.setY(object.getY() - 1);
if (
!this._isCollidingWith(
!this._isCollidingWithOneOf(
this._potentialCollidingObjects,
floorPlatformId,
/*excludeJumpthrus=*/
@@ -348,7 +348,7 @@ namespace gdjs {
//Stop when colliding with an obstacle.
while (
(this._requestedDeltaY < 0 &&
this._isCollidingWith(
this._isCollidingWithOneOf(
this._potentialCollidingObjects,
null,
/*excludeJumpThrus=*/
@@ -356,7 +356,7 @@ namespace gdjs {
)) ||
//Jumpthru = obstacle <=> Never when going up
(this._requestedDeltaY > 0 &&
this._isCollidingWithExcluding(
this._isCollidingWithOneOfExcluding(
this._potentialCollidingObjects,
this._overlappedJumpThru
))
@@ -427,48 +427,49 @@ namespace gdjs {
_checkGrabPlatform() {
const object = this.owner;
let tryGrabbingPlatform = false;
let oldX = object.getX();
object.setX(
object.getX() +
(this._requestedDeltaX > 0
? this._xGrabTolerance
: -this._xGrabTolerance)
);
let collidingPlatform = this._getCollidingPlatform();
if (collidingPlatform !== null && this._canGrab(collidingPlatform)) {
tryGrabbingPlatform = true;
}
object.setX(
object.getX() +
(this._requestedDeltaX > 0
? -this._xGrabTolerance
: this._xGrabTolerance)
const collidingPlatforms: PlatformRuntimeBehavior[] = gdjs.staticArray(
PlatformerObjectRuntimeBehavior.prototype._checkGrabPlatform
);
collidingPlatforms.length = 0;
for (const platform of this._potentialCollidingObjects) {
if (this._isCollidingWith(platform) && this._canGrab(platform)) {
collidingPlatforms.push(platform);
}
}
object.setX(oldX);
//Check if we can grab the collided platform
if (tryGrabbingPlatform) {
let oldY = object.getY();
let oldY = object.getY();
for (const collidingPlatform of collidingPlatforms) {
object.setY(
// @ts-ignore - collidingPlatform is guaranteed to be not null.
collidingPlatform.owner.getY() +
// @ts-ignore - collidingPlatform is guaranteed to be not null.
collidingPlatform.getYGrabOffset() -
this._yGrabOffset
);
if (
!this._isCollidingWith(
!this._isCollidingWithOneOf(
this._potentialCollidingObjects,
null,
/*excludeJumpthrus=*/
true
)
) {
this._setGrabbingPlatform(collidingPlatform!);
this._setGrabbingPlatform(collidingPlatform);
this._requestedDeltaY = 0;
} else {
object.setY(oldY);
collidingPlatforms.length = 0;
return;
}
object.setY(oldY);
}
collidingPlatforms.length = 0;
}
private _checkTransitionOnFloorOrFalling() {
@@ -553,48 +554,6 @@ namespace gdjs {
}
}
/**
* Among the platforms passed in parameter, return true if there is a platform colliding with the object.
* Ladders are *always* excluded from the test.
* @param candidates The platform to be tested for collision
* @param exceptThisOne The object identifier of a platform to be excluded from the check. Can be null.
* @param excludeJumpThrus If set to true, jumpthru platforms are excluded. false if not defined.
*/
_isCollidingWith(
candidates: gdjs.PlatformRuntimeBehavior[],
exceptThisOne?: number | null,
excludeJumpThrus?: boolean
) {
excludeJumpThrus = !!excludeJumpThrus;
for (let i = 0; i < candidates.length; ++i) {
const platform = candidates[i];
if (platform.owner.id === exceptThisOne) {
continue;
}
if (
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.LADDER
) {
continue;
}
if (
excludeJumpThrus &&
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.JUMPTHRU
) {
continue;
}
if (
gdjs.RuntimeObject.collisionTest(
this.owner,
platform.owner,
this._ignoreTouchingEdges
)
) {
return true;
}
}
return false;
}
/**
* Separate the object from all platforms passed in parameter.
* @param candidates The platform to be tested for collision
@@ -627,13 +586,55 @@ namespace gdjs {
return this.owner.separateFromObjects(objects, this._ignoreTouchingEdges);
}
/**
* Among the platforms passed in parameter, return true if there is a platform colliding with the object.
* Ladders are *always* excluded from the test.
* @param candidates The platform to be tested for collision
* @param exceptThisOne The object identifier of a platform to be excluded from the check. Can be null.
* @param excludeJumpThrus If set to true, jumpthru platforms are excluded. false if not defined.
*/
_isCollidingWithOneOf(
candidates: gdjs.PlatformRuntimeBehavior[],
exceptThisOne?: number | null,
excludeJumpThrus?: boolean
) {
excludeJumpThrus = !!excludeJumpThrus;
for (let i = 0; i < candidates.length; ++i) {
const platform = candidates[i];
if (platform.owner.id === exceptThisOne) {
continue;
}
if (
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.LADDER
) {
continue;
}
if (
excludeJumpThrus &&
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.JUMPTHRU
) {
continue;
}
if (
gdjs.RuntimeObject.collisionTest(
this.owner,
platform.owner,
this._ignoreTouchingEdges
)
) {
return true;
}
}
return false;
}
/**
* Among the platforms passed in parameter, return true if there is a platform colliding with the object.
* Ladders are *always* excluded from the test.
* @param candidates The platform to be tested for collision
* @param exceptTheseOnes The platforms to be excluded from the test
*/
private _isCollidingWithExcluding(
private _isCollidingWithOneOfExcluding(
candidates: gdjs.PlatformRuntimeBehavior[],
exceptTheseOnes: gdjs.PlatformRuntimeBehavior[]
) {
@@ -685,6 +686,23 @@ namespace gdjs {
return null;
}
/**
* Return true if the platform is colliding with the behavior owner object.
* Overlapped jump thru and ladders are excluded.
* @param platform The platform to be tested for collision
*/
private _isCollidingWith(platform: gdjs.PlatformRuntimeBehavior): boolean {
return (
platform.getPlatformType() !== gdjs.PlatformRuntimeBehavior.LADDER &&
!this._isIn(this._overlappedJumpThru, platform.owner.id) &&
gdjs.RuntimeObject.collisionTest(
this.owner,
platform.owner,
this._ignoreTouchingEdges
)
);
}
/**
* Update _overlappedJumpThru member, so that it contains all the jumpthru platforms colliding with
* the behavior owner object.
@@ -1317,7 +1335,7 @@ namespace gdjs {
let step = 0;
let noMoreOnFloor = false;
while (
!behavior._isCollidingWith(behavior._potentialCollidingObjects)
!behavior._isCollidingWithOneOf(behavior._potentialCollidingObjects)
) {
if (
step >

View File

@@ -191,67 +191,6 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
}
});
it('can grab, and release, a platform', function () {
//Put the object near the right ledge of the platform.
object.setPosition(
platform.getX() + platform.getWidth() + 2,
platform.getY() - 10
);
for (let i = 0; i < 35; ++i) {
object.getBehavior('auto1').simulateLeftKey();
runtimeScene.renderAndStep(1000 / 60);
}
//Check that the object grabbed the platform
expect(object.getX()).to.be.within(
platform.getX() + platform.getWidth() + 0,
platform.getX() + platform.getWidth() + 1
);
expect(object.getY()).to.be(platform.getY());
object.getBehavior('auto1').simulateReleaseKey();
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
true
);
}
//Check that the object is falling
expect(object.getY()).to.be(3.75);
});
it('can grab a platform and jump', function () {
//Put the object near the right ledge of the platform.
object.setPosition(
platform.getX() + platform.getWidth() + 2,
platform.getY() - 10
);
for (let i = 0; i < 35; ++i) {
object.getBehavior('auto1').simulateLeftKey();
runtimeScene.renderAndStep(1000 / 60);
}
//Check that the object grabbed the platform
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
expect(object.getX()).to.be.within(
platform.getX() + platform.getWidth() + 0,
platform.getX() + platform.getWidth() + 1
);
expect(object.getY()).to.be(platform.getY());
object.getBehavior('auto1').simulateJumpKey();
//Check that the object is jumping
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isJumping()).to.be(true);
}
expect(object.getY()).to.be.below(platform.getY());
});
it('can track object height changes', function () {
//Put the object near the right ledge of the platform.
object.setPosition(
@@ -337,6 +276,170 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
});
});
describe('(grab platforms)', function () {
let runtimeScene;
let object;
beforeEach(function () {
runtimeScene = makeTestRuntimeScene();
// Put a platformer object in the air.
object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformerObjectBehavior',
name: 'auto1',
gravity: 900,
maxFallingSpeed: 1500,
acceleration: 500,
deceleration: 1500,
maxSpeed: 500,
jumpSpeed: 1500,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: 60,
},
],
});
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
object.setPosition(0, -100);
});
it('can grab, and release, a platform', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
runtimeScene.renderAndStep(1000 / 60);
//Put the object near the right ledge of the platform.
object.setPosition(
platform.getX() + platform.getWidth() + 2,
platform.getY() - 10
);
for (let i = 0; i < 35; ++i) {
object.getBehavior('auto1').simulateLeftKey();
runtimeScene.renderAndStep(1000 / 60);
}
//Check that the object grabbed the platform
expect(object.getX()).to.be.within(
platform.getX() + platform.getWidth() + 0,
platform.getX() + platform.getWidth() + 1
);
expect(object.getY()).to.be(platform.getY());
object.getBehavior('auto1').simulateReleaseKey();
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
true
);
}
//Check that the object is falling
expect(object.getY()).to.be(3.75);
});
[true, false].forEach((addTopPlatformFirst) => {
it('can grab every platform when colliding 2', function () {
// The 2 platforms will be simultaneously in collision
// with the object when it grabs one.
let upperPlatform, lowerPlatform;
if (addTopPlatformFirst) {
upperPlatform = addPlatformObject(runtimeScene);
upperPlatform.setPosition(0, -10);
upperPlatform.setCustomWidthAndHeight(60, 10);
lowerPlatform = addPlatformObject(runtimeScene);
lowerPlatform.setPosition(0, 0);
lowerPlatform.setCustomWidthAndHeight(60, 10);
} else {
lowerPlatform = addPlatformObject(runtimeScene);
lowerPlatform.setPosition(0, 0);
lowerPlatform.setCustomWidthAndHeight(60, 10);
upperPlatform = addPlatformObject(runtimeScene);
upperPlatform.setPosition(0, -10);
upperPlatform.setCustomWidthAndHeight(60, 10);
}
// Put the object near the right ledge of the platform.
object.setPosition(
upperPlatform.getX() + upperPlatform.getWidth() + 2,
upperPlatform.getY() - 10
);
runtimeScene.renderAndStep(1000 / 60);
for (let i = 0; i < 35; ++i) {
object.getBehavior('auto1').simulateLeftKey();
runtimeScene.renderAndStep(1000 / 60);
}
// Check that the object grabbed the upper platform
expect(object.getX()).to.be.within(
upperPlatform.getX() + upperPlatform.getWidth() + 0,
upperPlatform.getX() + upperPlatform.getWidth() + 1
);
expect(object.getY()).to.be(upperPlatform.getY());
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
// Release upper platform
object.getBehavior('auto1').simulateReleaseKey();
for (let i = 0; i < 35; ++i) {
object.getBehavior('auto1').simulateLeftKey();
runtimeScene.renderAndStep(1000 / 60);
}
// Check that the object grabbed the lower platform
expect(object.getX()).to.be.within(
lowerPlatform.getX() + lowerPlatform.getWidth() + 0,
lowerPlatform.getX() + lowerPlatform.getWidth() + 1
);
expect(object.getY()).to.be(lowerPlatform.getY());
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
});
});
it('can grab a platform and jump', function () {
// Put a platform.
platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
runtimeScene.renderAndStep(1000 / 60);
//Put the object near the right ledge of the platform.
object.setPosition(
platform.getX() + platform.getWidth() + 2,
platform.getY() - 10
);
for (let i = 0; i < 35; ++i) {
object.getBehavior('auto1').simulateLeftKey();
runtimeScene.renderAndStep(1000 / 60);
}
//Check that the object grabbed the platform
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
expect(object.getX()).to.be.within(
platform.getX() + platform.getWidth() + 0,
platform.getX() + platform.getWidth() + 1
);
expect(object.getY()).to.be(platform.getY());
object.getBehavior('auto1').simulateJumpKey();
//Check that the object is jumping
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isJumping()).to.be(true);
}
expect(object.getY()).to.be.below(platform.getY());
});
});
describe('(jump and jump sustain, round coordinates on)', function () {
let runtimeScene;
let object;

View File

@@ -64,10 +64,10 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
obj.AddAction("Line",
_("Line"),
_("Draw a line on screen"),
_("Draw from _PARAM1_;_PARAM2_ to _PARAM3_;_PARAM4_ a line "
"(thickness: _PARAM5_) "
"with _PARAM0_"),
"Draw a line on screen",
"Draw from _PARAM1_;_PARAM2_ to _PARAM3_;_PARAM4_ a line "
"(thickness: _PARAM5_) "
"with _PARAM0_",
_("Drawing"),
"res/actions/line24.png",
"res/actions/line.png")

View File

@@ -147,9 +147,9 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
.SetIncludeFile("TiledSpriteObject/TiledSpriteObject.h");
obj.AddCondition("Angle",
_("Angle"),
_("Test the angle of a Tiled Sprite."),
_("the angle"),
"Angle",
"Test the angle of a Tiled Sprite.",
"the angle",
_("Size and angle"),
"res/conditions/rotate24.png",
"res/conditions/rotate.png")

View File

@@ -5,11 +5,11 @@ Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include <iostream>
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Tools/Localization.h"
#include <iostream>
void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension);
/**
@@ -68,17 +68,20 @@ class TopDownMovementBehaviorJsExtension : public gd::PlatformExtension {
.SetGetter("getAngleOffset");
autConditions["TopDownMovementBehavior::AngleOffset"].SetFunctionName(
"getAngleOffset");
autConditions["TopDownMovementBehavior::Angle"].SetFunctionName(
"getAngle");
autConditions["TopDownMovementBehavior::Angle"].SetFunctionName("getAngle");
autConditions["TopDownMovementBehavior::XVelocity"].SetFunctionName(
"getXVelocity");
autConditions["TopDownMovementBehavior::YVelocity"].SetFunctionName(
"getYVelocity");
autActions["TopDownMovementBehavior::SetMovementAngleOffset"]
.SetFunctionName("setMovementAngleOffset")
.SetGetter("getMovementAngleOffset");
autConditions["TopDownMovementBehavior::MovementAngleOffset"].SetFunctionName(
"getMovementAngleOffset");
autActions
["TopDownMovementBehavior::TopDownMovementBehavior::"
"SetMovementAngleOffset"]
.SetFunctionName("setMovementAngleOffset")
.SetGetter("getMovementAngleOffset");
autConditions
["TopDownMovementBehavior::TopDownMovementBehavior::"
"MovementAngleOffset"]
.SetFunctionName("getMovementAngleOffset");
autActions["TopDownMovementBehavior::AllowDiagonals"].SetFunctionName(
"allowDiagonals");
@@ -113,7 +116,8 @@ class TopDownMovementBehaviorJsExtension : public gd::PlatformExtension {
autExpressions["Angle"].SetFunctionName("getAngle");
autExpressions["XVelocity"].SetFunctionName("getXVelocity");
autExpressions["YVelocity"].SetFunctionName("getYVelocity");
autExpressions["MovementAngleOffset"].SetFunctionName("getMovementAngleOffset");
autExpressions["MovementAngleOffset"].SetFunctionName(
"getMovementAngleOffset");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};

View File

@@ -509,9 +509,56 @@ module.exports = {
false
)
.setDefaultValue('no')
.addParameter(
'yesorno',
_('Tween on the Hue/Saturation/Lightness (HSL)'),
'',
false
)
.setParameterLongDescription(
_('Useful to have a more natural change between colors.')
)
.setDefaultValue('no')
.getCodeExtraInformation()
.setFunctionName('addObjectColorTween');
behavior
.addAction(
'AddObjectColorHSLTween',
_('Add object HSL color tween'),
_(
'Add a tween animation for the object color using Hue/Saturation/Lightness. Hue can be any number, Saturation and Lightness are between 0 and 100. Use -1 for Saturation and Lightness to let them unchanged.'
),
_(
'Tween the color of _PARAM0_ using HSL to H: _PARAM3_ (_PARAM4_), S: _PARAM5_, L: _PARAM6_ with easing _PARAM7_ over _PARAM8_ms as _PARAM2_'
),
_('Color'),
'JsPlatform/Extensions/tween_behavior24.png',
'JsPlatform/Extensions/tween_behavior32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
.addParameter('string', _('Tween Identifier'), '', false)
.addParameter('expression', _('To Hue'), '', false)
.addParameter('yesorno', _('Animate Hue'), '', false)
.setDefaultValue('yes')
.addParameter('expression', _('To Saturation (-1 to ignore)'), '', false)
.setDefaultValue('-1')
.addParameter('expression', _('To Lightness (-1 to ignore)'), '', false)
.setDefaultValue('-1')
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.setDefaultValue('linear')
.addParameter('expression', _('Duration'), '', false)
.addParameter(
'yesorno',
_('Destroy this object when tween finishes'),
'',
false
)
.setDefaultValue('no')
.getCodeExtraInformation()
.setFunctionName('addObjectColorHSLTween');
behavior
.addCondition(
'Exists',

View File

@@ -4,6 +4,40 @@ namespace gdjs {
export type Tweenable = any;
}
function rgbToHsl(r: number, g: number, b: number): number[] {
r /= 255;
g /= 255;
b /= 255;
let v = Math.max(r, g, b),
c = v - Math.min(r, g, b),
f = 1 - Math.abs(v + v - c - 1);
let h =
c &&
(v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);
return [
Math.round(60 * (h < 0 ? h + 6 : h)),
Math.round((f ? c / f : 0) * 100),
Math.round(((v + v - c) / 2) * 100),
];
}
function hslToRgb(h: number, s: number, l: number): number[] {
h = h %= 360;
if (h < 0) {
h += 360;
}
s = s / 100;
l = l / 100;
const a = s * Math.min(l, 1 - l);
const f = (n = 0, k = (n + h / 30) % 12) =>
l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return [
Math.round(f(0) * 255),
Math.round(f(8) * 255),
Math.round(f(4) * 255),
];
}
export class TweenRuntimeBehavior extends gdjs.RuntimeBehavior {
/** @type Object.<string, TweenRuntimeBehavior.TweenInstance > */
_tweens: { [key: string]: TweenRuntimeBehavior.TweenInstance } = {};
@@ -644,15 +678,18 @@ namespace gdjs {
* @param easingValue Type of easing
* @param durationValue Duration in milliseconds
* @param destroyObjectWhenFinished Destroy this object when the tween ends
* @param useHSLColorTransition Tween using HSL color mappings, rather than direct RGB line
*/
addObjectColorTween(
identifier: string,
toColorStr: string,
easingValue: string,
durationValue: float,
destroyObjectWhenFinished: boolean
destroyObjectWhenFinished: boolean,
useHSLColorTransition: boolean
) {
const that = this;
if (!this._isActive) {
return;
}
@@ -674,30 +711,175 @@ namespace gdjs {
this.removeTween(identifier);
}
// @ts-ignore - objects are duck typed
const fromColor = this.owner.getColor().split(';');
let toColor = toColorStr.split(';');
const fromColor: string[] = this.owner.getColor().split(';');
const toColor: string[] = toColorStr.split(';');
if (toColor.length !== 3) {
return;
}
const newTweenable = TweenRuntimeBehavior.makeNewTweenable(
this._runtimeScene
);
if (useHSLColorTransition) {
const fromColorAsHSL = rgbToHsl(
parseFloat(fromColor[0]),
parseFloat(fromColor[1]),
parseFloat(fromColor[2])
);
const toColorAsHSL = rgbToHsl(
parseFloat(toColor[0]),
parseFloat(toColor[1]),
parseFloat(toColor[2])
);
newTweenable.setConfig({
from: {
hue: fromColorAsHSL[0],
saturation: fromColorAsHSL[1],
lightness: fromColorAsHSL[2],
},
to: {
hue: toColorAsHSL[0],
saturation: toColorAsHSL[1],
lightness: toColorAsHSL[2],
},
duration: durationValue,
easing: easingValue,
step: function step(state) {
const rgbFromHslColor = hslToRgb(
state.hue,
state.saturation,
state.lightness
);
// @ts-ignore - objects are duck typed
that.owner.setColor(
Math.floor(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
);
},
});
} else {
newTweenable.setConfig({
from: { red: fromColor[0], green: fromColor[1], blue: fromColor[2] },
to: { red: toColor[0], green: toColor[1], blue: toColor[2] },
duration: durationValue,
easing: easingValue,
step: function step(state) {
// @ts-ignore - objects are duck typed
that.owner.setColor(
Math.floor(state.red) +
';' +
Math.floor(state.green) +
';' +
Math.floor(state.blue)
);
},
});
}
this._addTween(
identifier,
newTweenable,
this._runtimeScene.getTimeManager().getTimeFromStart(),
durationValue
);
this._setupTweenEnding(identifier, destroyObjectWhenFinished);
}
/**
* Add an object color HSL tween, with the "to" color given using HSL (H: any number, S and L: 0-100).
* @param identifier Unique id to idenfify the tween
* @param toHue The target hue, or the same as the from color's hue if blank
* @param animateHue, include hue in calculations, as can't set this to -1 as default to ignore
* @param toSaturation The target saturation, or the same as the from color's saturation if blank
* @param toHue The target lightness, or the same as the from color's lightness if blank
* @param easingValue Type of easing
* @param durationValue Duration in milliseconds
* @param destroyObjectWhenFinished Destroy this object when the tween ends
*/
addObjectColorHSLTween(
identifier: string,
toHue: number,
animateHue: boolean,
toSaturation: number,
toLightness: number,
easingValue: string,
durationValue: float,
destroyObjectWhenFinished: boolean
) {
const that = this;
if (!this._isActive) {
return;
}
// @ts-ignore - objects are duck typed
if (!this.owner.getColor || !this.owner.setColor) {
return;
}
if (!!TweenRuntimeBehavior.easings[easingValue]) {
return;
}
if (this._tweenExists(identifier)) {
this.removeTween(identifier);
}
// @ts-ignore - objects are duck typed
const fromColor: string[] = this.owner.getColor().split(';');
if (fromColor.length < 3) return;
const fromColorAsHSL = rgbToHsl(
parseFloat(fromColor[0]),
parseFloat(fromColor[1]),
parseFloat(fromColor[2])
);
const toH = animateHue ? toHue : fromColorAsHSL[0];
const toS =
-1 === toSaturation
? fromColorAsHSL[1]
: Math.min(Math.max(toSaturation, 0), 100);
const toL =
-1 === toLightness
? fromColorAsHSL[2]
: Math.min(Math.max(toLightness, 0), 100);
const newTweenable = TweenRuntimeBehavior.makeNewTweenable(
this._runtimeScene
);
newTweenable.setConfig({
from: { red: fromColor[0], green: fromColor[1], blue: fromColor[2] },
to: { red: toColor[0], green: toColor[1], blue: toColor[2] },
from: {
hue: fromColorAsHSL[0],
saturation: fromColorAsHSL[1],
lightness: fromColorAsHSL[2],
},
to: {
hue: toH,
saturation: toS,
lightness: toL,
},
duration: durationValue,
easing: easingValue,
step: function step(state) {
const rgbFromHslColor = hslToRgb(
state.hue,
state.saturation,
state.lightness
);
// @ts-ignore - objects are duck typed
that.owner.setColor(
Math.floor(state.red) +
Math.floor(rgbFromHslColor[0]) +
';' +
Math.floor(state.green) +
Math.floor(rgbFromHslColor[1]) +
';' +
Math.floor(state.blue)
Math.floor(rgbFromHslColor[2])
);
},
});
this._addTween(
identifier,
newTweenable,

View File

@@ -0,0 +1,9 @@
/*
* GDevelop C++ Platform
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if !defined(GD_IDE_ONLY)
#include "GDCore/Project/EffectsContainer.cpp"
#endif

View File

@@ -0,0 +1,7 @@
/*
* GDevelop C++ Platform
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/EffectsContainer.h"

1
GDJS/.gitignore vendored
View File

@@ -1,2 +1 @@
/node_modules
/Runtime-dist

View File

@@ -13,6 +13,7 @@
#include "GDCore/CommonTools.h"
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
#include "GDCore/IDE/ProjectStripper.h"
#include "GDCore/Project/ExternalEvents.h"
@@ -26,11 +27,12 @@
#include "GDCore/Tools/Log.h"
#include "GDJS/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDJS/IDE/ExporterHelper.h"
#undef CopyFile // Disable an annoying macro
namespace gdjs {
Exporter::Exporter(gd::AbstractFileSystem& fileSystem, gd::String gdjsRoot_)
Exporter::Exporter(gd::AbstractFileSystem &fileSystem, gd::String gdjsRoot_)
: fs(fileSystem), gdjsRoot(gdjsRoot_) {
SetCodeOutputDirectory(fs.GetTempDir() + "/GDTemporaries/JSCodeTemp");
}
@@ -38,18 +40,20 @@ Exporter::Exporter(gd::AbstractFileSystem& fileSystem, gd::String gdjsRoot_)
Exporter::~Exporter() {}
bool Exporter::ExportProjectForPixiPreview(
const PreviewExportOptions& options) {
const PreviewExportOptions &options) {
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
return helper.ExportProjectForPixiPreview(options);
}
bool Exporter::ExportWholePixiProject(
gd::Project& project,
gd::Project &project,
gd::String exportDir,
std::map<gd::String, bool>& exportOptions) {
std::map<gd::String, bool> &exportOptions) {
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
gd::Project exportedProject = project;
auto usedExtensions = gd::UsedExtensionsFinder::ScanProject(project);
auto exportProject = [this, &exportedProject, &exportOptions, &helper](
gd::String exportDir) {
bool exportForCordova = exportOptions["exportForCordova"];
@@ -76,7 +80,12 @@ bool Exporter::ExportWholePixiProject(
// end of compatibility code
// Export engine libraries
helper.AddLibsInclude(true, false, false, includesFiles);
helper.AddLibsInclude(
/*pixiRenderers=*/true,
/*cocosRenderers=*/false,
/*websocketDebuggerClient=*/false,
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
// Export files for object and behaviors
helper.ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
@@ -141,13 +150,15 @@ bool Exporter::ExportWholePixiProject(
if (!exportProject(exportDir + "/www")) return false;
if (!helper.ExportCordovaFiles(exportedProject, exportDir)) return false;
if (!helper.ExportCordovaFiles(exportedProject, exportDir, usedExtensions))
return false;
} else if (exportOptions["exportForElectron"]) {
fs.MkDir(exportDir);
if (!exportProject(exportDir + "/app")) return false;
if (!helper.ExportElectronFiles(exportedProject, exportDir)) return false;
if (!helper.ExportElectronFiles(exportedProject, exportDir, usedExtensions))
return false;
} else if (exportOptions["exportForFacebookInstantGames"]) {
if (!exportProject(exportDir)) return false;
@@ -160,12 +171,12 @@ bool Exporter::ExportWholePixiProject(
return true;
}
bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
bool Exporter::ExportWholeCocos2dProject(gd::Project &project,
bool debugMode,
gd::String exportDir) {
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
wxProgressDialog* progressDialogPtr = NULL;
wxProgressDialog *progressDialogPtr = NULL;
// Prepare the export directory
fs.MkDir(exportDir);
@@ -185,7 +196,12 @@ bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
// end of compatibility code
// Export engine libraries
helper.AddLibsInclude(false, true, false, includesFiles);
helper.AddLibsInclude(
/*pixiRenderers=*/false,
/*cocosRenderers=*/true,
/*websocketDebuggerClient=*/false,
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
// Export files for object and behaviors
helper.ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);

View File

@@ -79,8 +79,12 @@ bool ExporterHelper::ExportProjectForPixiPreview(
gd::Project exportedProject = options.project;
// Always disable the splash for preview
exportedProject.GetLoadingScreen().ShowGDevelopSplash(false);
if (!options.fullLoadingScreen) {
// Most of the time, we skip the logo and minimum duration so that
// the preview start as soon as possible.
exportedProject.GetLoadingScreen().ShowGDevelopSplash(false);
exportedProject.GetLoadingScreen().SetMinDuration(0);
}
// Export resources (*before* generating events as some resources filenames
// may be updated)
@@ -96,7 +100,11 @@ bool ExporterHelper::ExportProjectForPixiPreview(
// end of compatibility code
// Export engine libraries
AddLibsInclude(true, false, true, includesFiles);
AddLibsInclude(/*pixiRenderers=*/true,
/*cocosRenderers=*/false,
/*websocketDebuggerClient=*/true,
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
// Export files for object and behaviors
ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
@@ -234,7 +242,8 @@ bool ExporterHelper::ExportPixiIndexFile(
}
bool ExporterHelper::ExportCordovaFiles(const gd::Project &project,
gd::String exportDir) {
gd::String exportDir,
std::set<gd::String> usedExtensions) {
auto &platformSpecificAssets = project.GetPlatformSpecificAssets();
auto &resourceManager = project.GetResourcesManager();
auto getIconFilename = [&resourceManager, &platformSpecificAssets](
@@ -294,7 +303,8 @@ bool ExporterHelper::ExportCordovaFiles(const gd::Project &project,
gd::String plugins = "";
auto dependenciesAndExtensions =
gd::ExportedDependencyResolver::GetDependenciesFor(project, "cordova");
gd::ExportedDependencyResolver::GetDependenciesFor(
project, usedExtensions, "cordova");
for (auto &dependencyAndExtension : dependenciesAndExtensions) {
const auto &dependency = dependencyAndExtension.GetDependency();
@@ -462,7 +472,8 @@ bool ExporterHelper::ExportFacebookInstantGamesFiles(const gd::Project &project,
}
bool ExporterHelper::ExportElectronFiles(const gd::Project &project,
gd::String exportDir) {
gd::String exportDir,
std::set<gd::String> usedExtensions) {
gd::String jsonName =
gd::Serializer::ToJSON(gd::SerializerElement(project.GetName()));
gd::String jsonPackageName =
@@ -489,7 +500,8 @@ bool ExporterHelper::ExportElectronFiles(const gd::Project &project,
gd::String packages = "";
auto dependenciesAndExtensions =
gd::ExportedDependencyResolver::GetDependenciesFor(project, "npm");
gd::ExportedDependencyResolver::GetDependenciesFor(
project, usedExtensions, "npm");
for (auto &dependencyAndExtension : dependenciesAndExtensions) {
const auto &dependency = dependencyAndExtension.GetDependency();
if (dependency.GetVersion() == "") {
@@ -594,12 +606,12 @@ bool ExporterHelper::CompleteIndexFile(
void ExporterHelper::AddLibsInclude(bool pixiRenderers,
bool cocosRenderers,
bool websocketDebuggerClient,
gd::String gdevelopLogoStyle,
std::vector<gd::String> &includesFiles) {
// First, do not forget common includes (they must be included before events
// generated code files).
InsertUnique(includesFiles, "libs/jshashtable.js");
InsertUnique(includesFiles, "gd.js");
InsertUnique(includesFiles, "gd-splash-image.js");
InsertUnique(includesFiles, "libs/rbush.js");
InsertUnique(includesFiles, "inputmanager.js");
InsertUnique(includesFiles, "jsonmanager.js");
@@ -631,6 +643,16 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "events-tools/windowtools.js");
InsertUnique(includesFiles, "events-tools/networktools.js");
if (gdevelopLogoStyle == "dark") {
InsertUnique(includesFiles, "splash/gd-logo-dark.js");
} else if (gdevelopLogoStyle == "dark-colored") {
InsertUnique(includesFiles, "splash/gd-logo-dark-colored.js");
} else if (gdevelopLogoStyle == "light-colored") {
InsertUnique(includesFiles, "splash/gd-logo-light-colored.js");
} else {
InsertUnique(includesFiles, "splash/gd-logo-light.js");
}
if (websocketDebuggerClient) {
InsertUnique(includesFiles, "websocket-debugger-client/hot-reloader.js");
InsertUnique(includesFiles,

View File

@@ -35,6 +35,7 @@ struct PreviewExportOptions {
: project(project_),
exportPath(exportPath_),
projectDataOnlyExport(false),
fullLoadingScreen(false),
nonRuntimeScriptsCacheBurst(0){};
/**
@@ -85,6 +86,15 @@ struct PreviewExportOptions {
return *this;
}
/**
* \brief Set if the export should show the full loading screen (false
* by default, skipping the minimum duration and GDevelop logo).
*/
PreviewExportOptions &SetFullLoadingScreen(bool enable) {
fullLoadingScreen = enable;
return *this;
}
/**
* \brief If set to a non zero value, the exported script URLs will have an
* extra search parameter added (with the given value) to ensure browser cache
@@ -103,6 +113,7 @@ struct PreviewExportOptions {
gd::String externalLayoutName;
std::map<gd::String, int> includeFileHashes;
bool projectDataOnlyExport;
bool fullLoadingScreen;
unsigned int nonRuntimeScriptsCacheBurst;
};
@@ -158,6 +169,7 @@ class ExporterHelper {
void AddLibsInclude(bool pixiRenderers,
bool cocosRenderers,
bool websocketDebuggerClient,
gd::String gdevelopLogoStyle,
std::vector<gd::String> &includesFiles);
/**
@@ -276,7 +288,9 @@ class ExporterHelper {
* \param project The project to be used to generate the configuration file.
* \param exportDir The directory where the config.xml must be created.
*/
bool ExportCordovaFiles(const gd::Project &project, gd::String exportDir);
bool ExportCordovaFiles(const gd::Project &project,
gd::String exportDir,
std::set<gd::String> usedExtensions);
/**
* \brief Generate the base Cocos2d files.
@@ -293,7 +307,9 @@ class ExporterHelper {
* \param project The project to be used to generate the files.
* \param exportDir The directory where the files must be created.
*/
bool ExportElectronFiles(const gd::Project &project, gd::String exportDir);
bool ExportElectronFiles(const gd::Project &project,
gd::String exportDir,
std::set<gd::String> usedExtensions);
/**
* \brief Generate the Facebook Instant Games files for packaging and save it

View File

@@ -5,11 +5,13 @@
*/
namespace gdjs {
export class LoadingScreenCocosRenderer {
render(percent) {
setPercent(percent) {
console.log('Loading ' + percent + '%');
}
unload() {}
unload() {
return Promise.resolve();
}
}
// Nothing to do

View File

@@ -357,10 +357,10 @@ namespace gdjs {
layerName: string,
rgbColor: string
) {
if (!runtimeScene.hasLayer(layerName)) {
return;
}
if (!runtimeScene.getLayer(layerName).isLightingLayer()) {
if (
!runtimeScene.hasLayer(layerName) ||
!runtimeScene.getLayer(layerName).isLightingLayer()
) {
return;
}
const colors = rgbColor.split(';');
@@ -370,9 +370,9 @@ namespace gdjs {
return runtimeScene
.getLayer(layerName)
.setClearColor(
parseInt(colors[0]),
parseInt(colors[1]),
parseInt(colors[2])
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
};
}

View File

@@ -103,6 +103,7 @@ namespace gdjs {
responseVar.setString(xhr.responseText);
} catch (e) {}
};
export const enableMetrics = function (
runtimeScene: gdjs.RuntimeScene,
enable: boolean
@@ -114,105 +115,33 @@ namespace gdjs {
* Convert a variable to JSON.
* @param variable The variable to convert to JSON
* @returns The JSON string representing the variable
*
* @deprecated Use `JSON.stringify(variable.toJSObject())` instead.
*/
export const variableStructureToJSON = function (
variable: gdjs.Variable
): string {
// TODO: Move this function to gdjs.Variable
if (variable.isPrimitive()) {
return JSON.stringify(variable.getValue());
} else if (variable.getType() === 'array') {
let str = '[';
let firstChild = true;
const children = variable.getAllChildren();
for (const p in children) {
if (children.hasOwnProperty(p)) {
if (!firstChild) {
str += ',';
}
str += variableStructureToJSON(children[p]);
firstChild = false;
}
}
str += ']';
return str;
} else if (variable.getType() === 'structure') {
let str = '{';
let firstChild = true;
const children = variable.getAllChildren();
for (const p in children) {
if (children.hasOwnProperty(p)) {
if (!firstChild) {
str += ',';
}
str +=
JSON.stringify(p) + ': ' + variableStructureToJSON(children[p]);
firstChild = false;
}
}
str += '}';
return str;
}
console.error('JSON conversion error: Variable type not recognized');
return '';
return JSON.stringify(variable.toJSObject());
};
export const objectVariableStructureToJSON = function (object, variable) {
return gdjs.evtTools.network.variableStructureToJSON(variable);
/**
* @deprecated Use `JSON.stringify(variable.toJSObject())` instead.
*/
export const objectVariableStructureToJSON = function (
object: gdjs.RuntimeObject,
variable: gdjs.Variable
): string {
return JSON.stringify(variable.toJSObject());
};
/**
* @deprecated Use `variable.fromJSObject` instead.
*/
export const _objectToVariable = function (
obj: any,
variable: gdjs.Variable
) {
if (obj === null) {
variable.setString('null');
} else if (typeof obj === 'number') {
if (Number.isNaN(obj)) {
console.warn('Variables cannot be set to NaN, setting it to 0.');
variable.setNumber(0);
} else {
variable.setNumber(obj);
}
} else if (typeof obj === 'string') {
variable.setString(obj);
} else if (typeof obj === 'undefined') {
// Do not modify the variable, as there is no value to set it to.
} else if (typeof obj === 'boolean') {
variable.setBoolean(obj);
} else if (Array.isArray(obj)) {
variable.castTo('array');
variable.clearChildren();
for (const i in obj) {
_objectToVariable(obj[i], variable.getChild(i));
}
} else if (typeof obj === 'object') {
variable.castTo('structure');
variable.clearChildren();
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
_objectToVariable(obj[p], variable.getChild(p));
}
}
} else if (typeof obj === 'symbol') {
variable.setString(obj.toString());
} else if (typeof obj === 'bigint') {
if (obj > Number.MAX_SAFE_INTEGER)
console.warn(
'Integers bigger than ' +
Number.MAX_SAFE_INTEGER +
" aren't supported by variables, it will be reduced to that size."
);
// @ts-ignore
variable.setNumber(parseInt(obj, 10));
} else if (typeof obj === 'function') {
console.error(
'Error: Impossible to set variable value to a function.'
);
} else {
console.error('Cannot identify type of object:', obj);
}
variable.fromJSObject(obj);
};
/**
@@ -221,30 +150,25 @@ namespace gdjs {
* @param jsonStr The JSON string
* @param variable The variable where to put the parsed JSON
* @returns true if JSON was properly parsed
*
* @deprecated Use `variable.fromJSON` instead.
*/
export const jsonToVariableStructure = function (
jsonStr: string,
variable: gdjs.Variable
): boolean {
// TODO: Move this function to gdjs.Variable
if (jsonStr.length === 0) {
return false;
}
try {
const obj = JSON.parse(jsonStr);
gdjs.evtTools.network._objectToVariable(obj, variable);
return true;
} catch (e) {
// Do nothing if JSON was not properly parsed.
return false;
}
): void {
variable.fromJSON(jsonStr);
};
/**
* @deprecated Use `variable.fromJSON` instead.
*/
export const jsonToObjectVariableStructure = function (
jsonStr,
object,
variable
jsonStr: string,
object: gdjs.RuntimeObject,
variable: gdjs.Variable
) {
gdjs.evtTools.network.jsonToVariableStructure(jsonStr, variable);
variable.fromJSON(jsonStr);
};
}
}

File diff suppressed because one or more lines are too long

View File

@@ -38,6 +38,9 @@ namespace gdjs {
export const callbacksRuntimeSceneUnloaded: Array<RuntimeSceneCallback> = [];
export const callbacksObjectDeletedFromScene: Array<RuntimeSceneRuntimeObjectCallback> = [];
/** Base64 encoded logo of GDevelop for the splash screen. */
export let gdevelopLogo: string = '';
/**
* Convert a rgb color value to a hex string.
*

View File

@@ -494,20 +494,10 @@ namespace gdjs {
* @param g Green color component in the range 0-255.
* @param b Blue color component in the range 0-255.
*/
setClearColor(
r: integer | null,
g: integer | null,
b: integer | null
): void {
if (r) {
this._clearColor[0] = r / 255;
}
if (g) {
this._clearColor[1] = g / 255;
}
if (b) {
this._clearColor[2] = b / 255;
}
setClearColor(r: integer, g: integer, b: integer): void {
this._clearColor[0] = r / 255;
this._clearColor[1] = g / 255;
this._clearColor[2] = b / 255;
this._renderer.updateClearColor();
}

View File

@@ -1,89 +1,255 @@
namespace gdjs {
import PIXI = GlobalPIXIModule.PIXI;
class LoadingScreenPixiRenderer {
_pixiRenderer: any;
_loadingScreen: any;
_progressText: any;
_madeWithText: any;
_websiteText: any;
_splashImage: any;
enum LoadingScreenState {
NOT_STARTED,
STARTED,
FINISHED,
}
constructor(runtimeGamePixiRenderer, loadingScreenSetup) {
const fadeIn = (
object: PIXI.DisplayObject | null,
duration: float,
deltaTimeInMs: float
) => {
if (!object) return;
if (duration > 0) {
object.alpha += ((1 / duration) * deltaTimeInMs) / 1000;
if (object.alpha > 1) object.alpha = 1;
} else {
object.alpha = 1;
}
};
const hasFadedIn = (object: PIXI.DisplayObject | null) => {
return !object || object.alpha >= 1;
};
class LoadingScreenPixiRenderer {
_pixiRenderer: PIXI.Renderer | null;
_loadingScreenData: LoadingScreenData;
_loadingScreenContainer: PIXI.Container;
_backgroundSprite: PIXI.Sprite | null = null;
_gdevelopLogoSprite: PIXI.Sprite | null = null;
_progressBarGraphics: PIXI.Graphics | null = null;
_state: LoadingScreenState = LoadingScreenState.NOT_STARTED;
_startTimeInMs: float = 0;
_backgroundReadyTimeInMs: float = 0;
_lastFrameTimeInMs: float = 0;
_progressPercent: float = 0;
constructor(
runtimeGamePixiRenderer: gdjs.RuntimeGamePixiRenderer,
imageManager: gdjs.PixiImageManager,
loadingScreenData: LoadingScreenData
) {
this._loadingScreenData = loadingScreenData;
this._loadingScreenContainer = new PIXI.Container();
this._pixiRenderer = runtimeGamePixiRenderer.getPIXIRenderer();
if (!this._pixiRenderer) {
// A PIXI Renderer can be missing during tests, when creating a runtime game
// without a canvas.
return;
}
this._loadingScreen = new PIXI.Container();
this._progressText = new PIXI.Text(' ', {
fontSize: '30px',
fontFamily: 'Arial',
fill: '#FFFFFF',
align: 'center',
});
this._loadingScreen.addChild(this._progressText);
if (loadingScreenSetup && loadingScreenSetup.showGDevelopSplash) {
this._madeWithText = new PIXI.Text('Made with', {
fontSize: '30px',
fontFamily: 'Arial',
fill: '#FFFFFF',
align: 'center',
});
this._madeWithText.position.y = this._pixiRenderer.height / 2 - 130;
this._websiteText = new PIXI.Text('gdevelop-app.com', {
fontSize: '30px',
fontFamily: 'Arial',
fill: '#FFFFFF',
align: 'center',
});
this._websiteText.position.y = this._pixiRenderer.height / 2 + 100;
this._splashImage = PIXI.Sprite.from(gdjs.splashImage);
this._splashImage.position.x = this._pixiRenderer.width / 2;
this._splashImage.position.y = this._pixiRenderer.height / 2;
this._splashImage.anchor.x = 0.5;
this._splashImage.anchor.y = 0.5;
this._splashImage.scale.x = this._pixiRenderer.width / 800;
this._splashImage.scale.y = this._pixiRenderer.width / 800;
this._loadingScreen.addChild(this._splashImage);
this._loadingScreen.addChild(this._madeWithText);
this._loadingScreen.addChild(this._websiteText);
this._pixiRenderer.backgroundColor = this._loadingScreenData.backgroundColor;
const backgroundTexture = imageManager.getPIXITexture(
loadingScreenData.backgroundImageResourceName
);
if (backgroundTexture !== imageManager.getInvalidPIXITexture()) {
this._backgroundSprite = PIXI.Sprite.from(backgroundTexture);
this._backgroundSprite.alpha = 0;
this._backgroundSprite.anchor.x = 0.5;
this._backgroundSprite.anchor.y = 0.5;
this._loadingScreenContainer.addChild(this._backgroundSprite);
}
if (loadingScreenData.showGDevelopSplash) {
this._gdevelopLogoSprite = PIXI.Sprite.from(gdjs.gdevelopLogo);
this._gdevelopLogoSprite.alpha = 0;
this._gdevelopLogoSprite.anchor.x = 0.5;
this._gdevelopLogoSprite.anchor.y = 0.5;
this._loadingScreenContainer.addChild(this._gdevelopLogoSprite);
}
if (loadingScreenData.showProgressBar) {
this._progressBarGraphics = new PIXI.Graphics();
this._progressBarGraphics.alpha = 0;
this._loadingScreenContainer.addChild(this._progressBarGraphics);
}
this._render(performance.now());
}
setPercent(percent: number) {
this._progressPercent = percent;
}
private _startLoadingScreen() {
if (!this._pixiRenderer) return;
this._state = LoadingScreenState.STARTED;
this._startTimeInMs = performance.now();
}
private _updatePositions() {
if (!this._pixiRenderer) return;
if (this._backgroundSprite && this._backgroundSprite.texture.valid) {
this._backgroundSprite.position.x = this._pixiRenderer.width / 2;
this._backgroundSprite.position.y = this._pixiRenderer.height / 2;
const scale = Math.max(
this._pixiRenderer.width / this._backgroundSprite.texture.width,
this._pixiRenderer.height / this._backgroundSprite.texture.height
);
this._backgroundSprite.scale.x = scale;
this._backgroundSprite.scale.y = scale;
}
if (this._gdevelopLogoSprite) {
this._gdevelopLogoSprite.position.x = this._pixiRenderer.width / 2;
this._gdevelopLogoSprite.position.y = this._pixiRenderer.height / 2;
const logoWidth = 680;
const border =
this._pixiRenderer.width > this._pixiRenderer.height &&
this._pixiRenderer.width > 500
? 150
: 35;
const desiredWidth = Math.min(
logoWidth,
Math.max(1, this._pixiRenderer.width - border * 2)
);
const scale = desiredWidth / logoWidth;
this._gdevelopLogoSprite.scale.x = scale;
this._gdevelopLogoSprite.scale.y = scale;
// Give up trying to show the logo if the resolution is really too small.
// TODO: use a low resolution logo instead.
this._gdevelopLogoSprite.visible =
this._pixiRenderer.width > 200 && this._pixiRenderer.height > 200;
}
}
render(percent) {
private _render(timeInMs: float) {
if (!this._pixiRenderer) {
return;
}
const screenBorder = 10;
if (this._madeWithText) {
this._madeWithText.position.x =
this._pixiRenderer.width / 2 - this._madeWithText.width / 2;
this._madeWithText.position.y =
this._pixiRenderer.height / 2 -
this._splashImage.height / 2 -
this._madeWithText.height -
20;
// Continue the rendering loop as long as the loading screen is not finished.
if (this._state !== LoadingScreenState.FINISHED) {
requestAnimationFrame(() => this._render(performance.now()));
}
if (this._websiteText) {
this._websiteText.position.x =
this._pixiRenderer.width - this._websiteText.width - screenBorder;
this._websiteText.position.y =
this._pixiRenderer.height - this._websiteText.height - screenBorder;
const deltaTimeInMs = this._lastFrameTimeInMs
? timeInMs - this._lastFrameTimeInMs
: 0;
this._lastFrameTimeInMs = timeInMs;
this._updatePositions();
if (this._state == LoadingScreenState.NOT_STARTED) {
if (!this._backgroundSprite || this._backgroundSprite.texture.valid) {
this._startLoadingScreen();
}
} else if (this._state == LoadingScreenState.STARTED) {
const backgroundFadeInDuration = this._loadingScreenData
.backgroundFadeInDuration;
fadeIn(this._backgroundSprite, backgroundFadeInDuration, deltaTimeInMs);
if (hasFadedIn(this._backgroundSprite)) {
if (!this._backgroundReadyTimeInMs)
this._backgroundReadyTimeInMs = timeInMs;
const logoAndProgressFadeInDuration = this._loadingScreenData
.logoAndProgressFadeInDuration;
const logoAndProgressLogoFadeInDelay = this._loadingScreenData
.logoAndProgressLogoFadeInDelay;
if (
timeInMs - this._backgroundReadyTimeInMs >
logoAndProgressLogoFadeInDelay * 1000
) {
fadeIn(
this._gdevelopLogoSprite,
logoAndProgressFadeInDuration,
deltaTimeInMs
);
fadeIn(
this._progressBarGraphics,
logoAndProgressFadeInDuration,
deltaTimeInMs
);
}
}
if (this._progressBarGraphics) {
const color = this._loadingScreenData.progressBarColor;
let progressBarWidth =
(this._loadingScreenData.progressBarWidthPercent / 100) *
this._pixiRenderer.width;
if (this._loadingScreenData.progressBarMaxWidth > 0) {
if (progressBarWidth > this._loadingScreenData.progressBarMaxWidth)
progressBarWidth = this._loadingScreenData.progressBarMaxWidth;
}
if (this._loadingScreenData.progressBarMinWidth > 0) {
if (progressBarWidth < this._loadingScreenData.progressBarMinWidth)
progressBarWidth = this._loadingScreenData.progressBarMinWidth;
}
const progressBarHeight = this._loadingScreenData.progressBarHeight;
const progressBarX = Math.floor(
this._pixiRenderer.width / 2 - progressBarWidth / 2
);
const progressBarY =
this._pixiRenderer.height < 350
? Math.floor(this._pixiRenderer.height - 10 - progressBarHeight)
: Math.floor(this._pixiRenderer.height - 90 - progressBarHeight);
const lineWidth = 1;
// Display bar with an additional 1% to ensure it's filled at the end.
const progress = Math.min(1, (this._progressPercent + 1) / 100);
this._progressBarGraphics.clear();
this._progressBarGraphics.lineStyle(lineWidth, color, 1, 0);
this._progressBarGraphics.drawRect(
progressBarX,
progressBarY,
progressBarWidth,
progressBarHeight
);
this._progressBarGraphics.beginFill(color, 1);
this._progressBarGraphics.lineStyle(0, color, 1);
this._progressBarGraphics.drawRect(
progressBarX + lineWidth,
progressBarY + lineWidth,
progressBarWidth * progress - lineWidth * 2,
progressBarHeight - lineWidth * 2
);
this._progressBarGraphics.endFill();
}
}
this._progressText.text = percent + '%';
this._progressText.position.x = screenBorder;
this._progressText.position.y =
this._pixiRenderer.height - this._progressText.height - screenBorder;
this._pixiRenderer.render(this._loadingScreen);
this._pixiRenderer.render(this._loadingScreenContainer);
}
unload() {}
unload(): Promise<void> {
const totalElapsedTime = (performance.now() - this._startTimeInMs) / 1000;
const remainingTime =
this._loadingScreenData.minDuration - totalElapsedTime;
this.setPercent(100);
// Ensure we have shown the loading screen for at least minDuration.
if (remainingTime <= 0) {
this._state = LoadingScreenState.FINISHED;
return Promise.resolve();
}
return new Promise((resolve) =>
setTimeout(() => {
this._state = LoadingScreenState.FINISHED;
resolve();
}, remainingTime * 1000)
);
}
}
// Nothing to do
export const LoadingScreenRenderer = LoadingScreenPixiRenderer;
//Register the class to let the engine use it.
export const LoadingScreenRenderer = LoadingScreenPixiRenderer;
}

View File

@@ -65,6 +65,9 @@ namespace gdjs {
const res = this._resources[i];
if (res.name === resourceName && res.kind === 'image') {
texture = PIXI.Texture.from(res.file);
if (!res.smoothed) {
texture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
}
break;
}
}

Some files were not shown because too many files have changed in this diff Show More