Compare commits

...

327 Commits

Author SHA1 Message Date
Florian Rival
cf4f70bf48 Formatting 2025-09-30 20:03:02 +02:00
Florian Rival
43fa6aa3f5 Add a grace distance to Destroy Outside behavior to avoid deleting objects never seen near the screen 2025-09-30 17:33:19 +02:00
Clément Pasteau
add042ec16 Create a standalone page that can be used for bundles (#7861) 2025-09-30 14:20:53 +02:00
Clément Pasteau
e174136fb4 New Save & Load built-in actions (#7811)
* New actions are available to save & load the game state, making Saving & Loading as easy as adding 1 action to your game!
* While it will work in most cases, it has a few limitations and hasn't been thoroughly tested on all types of objects/behaviors and games, so it is for the moment tagged as **Experimental** while we gather feedback and improve it
* Check out the wiki for more info: https://wiki.gdevelop.io/gdevelop5/all-features/save-state
2025-09-29 15:38:37 +02:00
github-actions[bot]
ff8697ed71 [Auto PR] Update translations (#7852) 2025-09-26 10:23:40 +02:00
ViktorVovk
542a841791 Add error handling for Howler sound methods (#7853)
* Specifically for "Maximum call stack size exceeded" errors.
2025-09-26 10:11:58 +02:00
github-actions[bot]
b9640f0049 [Auto PR] Update translations (#7851)
Do not show in changelog
2025-09-25 17:51:05 +02:00
Florian Rival
a6cc3dc85b Update storage action to make clearer the usage of manual load/unload 2025-09-25 16:28:36 +02:00
Clément Pasteau
6083c71e0e Bump newIDE version (#7850) 2025-09-25 10:48:53 +02:00
github-actions[bot]
e844ea819b Update extension translations [skip ci] (#7849)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-25 09:38:37 +02:00
github-actions[bot]
27f0300bc7 Update translations [skip ci] (#7845)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-25 09:35:00 +02:00
Clément Pasteau
35cea2eaba Update example slug info for analytics (#7847)
Do not show in changelog
2025-09-24 14:42:11 +02:00
D8H
7c4617da99 Add a skybox effect (#7843) 2025-09-23 21:14:44 +02:00
Clément Pasteau
d6d7c5c1fb Fix templates not accessible when bought via an unlisted bundle (#7844) 2025-09-23 11:18:15 +02:00
github-actions[bot]
b7f7a39aa7 Update translations [skip ci] (#7840)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-22 17:05:31 +02:00
Clément Pasteau
8c04771a87 Allow opening a bundle with a link via category (#7842)
Do not show in changelog
2025-09-22 16:36:16 +02:00
Clément Pasteau
febf15b279 Slightly improve Course page overload (#7841)
Do not show in changelog
2025-09-22 13:58:55 +02:00
D8H
a05e4b7ecc Fix groups wrongly underlined in red while having the right behaviors in the Events Sheet (#7839) 2025-09-22 11:25:37 +02:00
Florian Rival
3568a999f9 Update README [skip ci] [ci skip] 2025-09-18 16:48:48 +02:00
Florian Rival
207097bf03 Bump newIDE version 2025-09-18 10:01:44 +02:00
github-actions[bot]
0e3d2b9570 Update translations [skip ci] (#7831)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-09-18 10:01:32 +02:00
D8H
87fac429e8 Underline groups having missing behaviors in the Events Sheet (#7832) 2025-09-18 10:00:28 +02:00
D8H
3b1097931b Fix child object creation when called from another function (#7836) 2025-09-17 18:38:06 +02:00
D8H
ec7e408cd1 Fix button labels no longer updating in the editor (#7835) 2025-09-17 15:37:39 +02:00
Florian Rival
f8a99b9cfa Add prices and currency to store analytics (#7833)
Don't show in changelog
2025-09-16 15:44:35 +02:00
D8H
4c7231e6ae Fix a crash at runtime when behaviors are missing in functions (#7830) 2025-09-15 16:40:21 +02:00
D8H
883d32515c Rename "community" extensions as "experimental" extensions (#7828) 2025-09-15 14:04:31 +02:00
Florian Rival
f0f3c257fa Bump newIDE version 2025-09-14 16:11:02 +02:00
github-actions[bot]
42fce7d9ce Update translations [skip ci] (#7810)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-09-14 16:10:35 +02:00
Florian Rival
facac37fff Fix opening of the scene after creating project with AI
Don't show in changelog
2025-09-14 15:44:04 +02:00
Florian Rival
1272b601c6 Add many AI improvements (#7819)
* Allow the AI to start a game from a template
* Improved instances manipulation
* Better variables support
* Allow AI to swap an object by another one from the asset store
* Allow AI to change multiple properties at the same time
* Fix AI not properly removing behaviors associated to a behavior just removed
2025-09-13 18:01:35 +02:00
D8H
58e35cfaf5 Allow extensions to define labels for properties with string selectors (#7825) 2025-09-12 18:30:10 +02:00
D8H
2befc9781b Fix the list of assets suggested for sliders and toggle switches (#7826) 2025-09-12 15:42:10 +02:00
Florian Rival
8fb2872c36 Disable spell check on comments
Don't show in changelog
2025-09-12 10:25:41 +02:00
D8H
98033515c8 Fix a 1-frame delay when applying anchors to child-objects (#7820) 2025-09-11 15:19:45 +02:00
D8H
25c02cea2e Add a link to an help page for asynchronous functions (#7823) 2025-09-11 14:53:15 +02:00
D8H
6d5be78fec Forbid to import an extension which has the same name as a built-in one (#7822) 2025-09-11 14:52:49 +02:00
D8H
f6e60085db Fix missing extension dependencies in exported assets (#7821) 2025-09-11 14:52:32 +02:00
Florian Rival
a61648af70 Fix landscape/portrait orientation not working on iOS 2025-09-11 10:50:06 +02:00
D8H
2b496c6fd3 Fix custom object extraction behavior check (#7818)
- Don't show in changelog
2025-09-10 12:30:48 +02:00
Florian Rival
1e984f0965 Fix the chosen preset for AI not always valid when switching between chat and agent 2025-09-08 18:18:53 +02:00
Florian Rival
37bed36315 Make comment events editable exactly as they look (#7812) 2025-09-05 14:46:42 +02:00
D8H
5394cc5201 Forbid to add Physics behaviors on objects inside custom objects (#7809) 2025-09-04 19:37:57 +02:00
Florian Rival
5e3dfb0e9c Add condition to check if a key was just pressed (#7808) 2025-09-04 17:32:25 +02:00
Clément Pasteau
f6a6c981f8 Add a small intro explaining how to follow the courses (#7807) 2025-09-04 16:10:37 +02:00
Clément Pasteau
415c1bfd2f Fix bundles showing up in new object search (#7806) 2025-09-03 14:11:16 +02:00
github-actions[bot]
e4a911db25 Update translations [skip ci] (#7804)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-03 10:42:14 +02:00
Clément Pasteau
5fcd67d77b Additional bundles are added to the Learn & Shop sections (#7805)
* A premium bundle, including multiple courses, asset packs, templates and a gold subscription
* A curated platformer-specific bundle, including everything needed to learn & create a platformer game
2025-09-03 10:30:24 +02:00
Florian Rival
86db08ac3f Fix npm start on Windows
Don't show in changelog
2025-09-01 15:49:21 +02:00
github-actions[bot]
8d735fc726 Update translations [skip ci] (#7800)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-01 14:32:50 +02:00
Clément Pasteau
1e1f4bb2a3 Merge Bundle page into 1 component + small design fixes (#7803)
Do not show in changelog
2025-09-01 14:17:33 +02:00
Florian Rival
d8000aca10 Simplify editor README [skip ci] [ci skip] 2025-09-01 00:00:14 +02:00
Florian Rival
a2660ff0dc Allow to easily work on the game engine using the web-app development version (#7802) 2025-08-31 23:54:44 +02:00
Florian Rival
000d5785cf Improve AI agent performance by removing old object properties
Don't show in changelog
2025-08-30 17:29:49 +02:00
Florian Rival
9fe04712a9 Improve AI ability to update existing instances 2025-08-30 16:59:09 +02:00
Florian Rival
846afd9e0a Improve documentation with reference of all available effects. 2025-08-30 14:16:00 +02:00
Florian Rival
6125ff0f90 Allow AI to change layers/effects/scene and some game properties (#7799) 2025-08-29 19:15:50 +02:00
github-actions[bot]
a5428a8843 Update translations [skip ci] (#7798)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-08-29 15:58:38 +02:00
Ansel Games
19be45cda6 Fix grammar in text font size change warning (#7797) 2025-08-29 14:26:04 +02:00
Clément Pasteau
889c97cb27 Update Bundle Page with more details about content and limited offer (#7795) 2025-08-29 14:24:49 +02:00
Florian Rival
1d83da41a9 Improve physics extensions descriptions to tell about the scale and typical force values 2025-08-27 16:42:41 +02:00
github-actions[bot]
a65f2174eb Update translations [skip ci] (#7781)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-08-27 10:05:59 +02:00
D8H
9db493e87e Refactor tab opening (#7794)
- Don't show in changelogs
2025-08-26 17:29:42 +02:00
Florian Rival
49a3a18b51 Make clear in-app tutorials are free
Don't show in changelog
2025-08-26 15:53:55 +02:00
Florian Rival
0489e7036b Rework stories for TeamSection 2025-08-26 12:07:57 +02:00
Florian Rival
794d5a781c Add support for setting a full name for a student account (#7788) 2025-08-26 11:09:19 +02:00
D8H
c21dfbcc1f Optimize used resources search during export (#7790) 2025-08-25 20:12:17 +02:00
Clément Pasteau
cc75db6d09 Fix crash when accessing an owned archived product in the store (#7789)
Do not show in changelog
2025-08-25 17:03:30 +02:00
Florian Rival
48d35a50b5 Fix wrong redirection to AI tab when using initial-dialog=ask-ai in the web-app 2025-08-15 11:06:40 +02:00
Florian Rival
3a0888046f Add a selector to switch the AI used or choose a preset (#7782) 2025-08-14 16:14:39 +02:00
github-actions[bot]
7917994835 Update translations (#7763)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-08-14 13:55:44 +02:00
Florian Rival
9e2bab43f7 Rework display of course specializations
Don't show in changelog
2025-08-14 13:54:41 +02:00
Florian Rival
7e03f47f08 Bump newIDE version 2025-08-14 12:37:27 +02:00
Florian Rival
7c6137a4fc Ensure behaviors used in events generated by AI are automatically added to objects 2025-08-13 18:02:24 +02:00
D8H
0cbd6e2fe9 Fix cached materials not being cleared when unloading resources (#7780) 2025-08-11 15:50:22 +02:00
D8H
5acc1f5560 Remove unused imports (#7779)
Don't show in changelog
2025-08-11 12:30:49 +02:00
D8H
887693a90d Forbid camera zoom to be set to 0 (#7778) 2025-08-11 10:30:28 +02:00
D8H
fbb985833f Optimize JavaScript events (avoid a list copy) (#7775)
Only show in developer changelog
2025-08-09 17:58:12 +02:00
D8H
1b3734ff6b Add the unit for 3D angle parameter descriptions (#7774) 2025-08-08 17:13:06 +02:00
D8H
6288b30ac3 Add an alert when editing the default variant of an extension from the store (#7773) 2025-08-08 16:39:50 +02:00
D8H
ee435f7081 Fix the tint action of Sprite to handle floating point color components (#7772) 2025-08-08 15:57:19 +02:00
Gleb Volkov
d75b4eb2a9 Add debug flag to GDJS build script (#7771) 2025-08-08 14:17:26 +02:00
Florian Rival
5eeb505807 Fix tests
Don't show in changelog
2025-08-08 11:36:33 +02:00
Florian Rival
30566e35ce Fix changing language not reloading courses in this language 2025-08-08 10:14:57 +02:00
Florian Rival
e058b7f295 Add line height property for Text objects (#7769) 2025-08-07 18:47:02 +02:00
Florian Rival
902a30a9f8 Update capability user-friendly name and fix test
Don't show in changelog
2025-08-07 16:55:52 +02:00
Florian Rival
8669b94fb0 Improve project information sent to AI with the game resolution
Don't show in changelog
2025-08-05 14:29:58 +02:00
Florian Rival
7fb08aea62 Fix default light effects not added for scenes created by AI 2025-08-04 21:35:52 +02:00
Florian Rival
e7a1548b0e Fix default UI layer position for AI 2025-08-04 15:36:33 +02:00
Florian Rival
bdcb6f0533 Improve extension descriptions 2025-08-04 15:36:21 +02:00
github-actions[bot]
97849ce6f1 Update translations [skip ci] (#7760)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-08-01 18:05:51 +02:00
Clément Pasteau
d1c937caf4 Allow redeeming a code from the profile page directly (#7761) 2025-08-01 17:56:43 +02:00
Florian Rival
5ffe6279a2 Fix warning 2025-08-01 16:08:28 +02:00
Clément Pasteau
9260e2b77a Improve bundle listing (#7759)
Do not show in changelog
2025-08-01 15:44:16 +02:00
D8H
593465e2ec Optimize event-function calls (#7758) 2025-08-01 15:19:26 +02:00
github-actions[bot]
8820350760 Update translations [skip ci] (#7756)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-08-01 14:12:21 +02:00
Florian Rival
7e1668229a Change how bundle price is calculated to avoid API calls (#7757) 2025-08-01 13:50:26 +02:00
D8H
387b96b9a0 Allow anchors to set the wrapping width of bitmap texts and BBCode texts (#7755) 2025-08-01 11:47:10 +02:00
github-actions[bot]
5f52d786c6 Update translations [skip ci] (#7749)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-31 14:54:34 +02:00
Clément Pasteau
e0db597f9d A new Bundle to get started with GDevelop is now available (#7751)
* including multiple game templates and asset packs
* including a redemption code for a premium subscription
* including multiple official game dev courses
2025-07-31 14:18:05 +02:00
Florian Rival
41b0315ec6 Improve rating banner for course chapters 2025-07-30 18:49:27 +02:00
Florian Rival
a930a4085e Add basic button to rate premium course chapter 2025-07-30 14:56:46 +02:00
Florian Rival
d0dbbfac07 Add "StrReplaceOne" and "StrReplaceAll" expressions (#7750) 2025-07-30 10:10:29 +02:00
Florian Rival
3dc24b46f4 Fix warning
Don't show in changelog
2025-07-28 13:54:12 +02:00
Florian Rival
8e44a357b4 Fix Android build and player authentication sometimes not working (#7748)
- Player authentication window could not open if no action/condition related to player authentication was used
- Fix Android build by using an updated dependency for opening the authentication window
2025-07-28 12:55:58 +02:00
Florian Rival
dd462310cc Reduce network requests at startup by lazily loading course chapters when opened 2025-07-26 16:37:38 +02:00
Florian Rival
a1935fa0cd Reduce a bit more unnecessary fetches for course chapters
Don't show in changelog
2025-07-26 14:38:54 +02:00
Florian Rival
b45c57246b Add animation names to inspected object properties for AI 2025-07-26 14:21:44 +02:00
Florian Rival
c481ecd6b5 Bump newIDE version 2025-07-25 15:47:07 +02:00
github-actions[bot]
e0898dd9b0 Update translations [skip ci] (#7737)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-07-25 13:40:41 +02:00
Clément Pasteau
5561334efa Fix Send icon color (#7745) 2025-07-25 13:33:02 +02:00
Florian Rival
6c4bb4f79e Fix selection of face orientation in 3D Cube editor 2025-07-25 10:04:14 +02:00
D8H
8b2d2e2fe7 Fix "edit children" drop-down menu item activation (#7744)
- don't show in changelog
2025-07-24 14:04:25 +02:00
Florian Rival
49d128c964 Display "Ask AI" as a separate pane (or drawer on small screens) (#7738) 2025-07-24 13:20:52 +02:00
D8H
f24d1e0916 Add a deprecation message for custom objects using old "configuration overriding" (#7742) 2025-07-24 13:19:23 +02:00
D8H
9faa4c0c69 Fix button labels not refreshing when modified from the side panel (#7741) 2025-07-24 10:32:48 +02:00
D8H
a04b8f65db Allow to select a custom object variant in the properties panel (#7740)
* Also show a dialog to duplicate a variant before opening them if necessary.
2025-07-23 17:26:03 +02:00
D8H
e1cf7d23cd Various fixes for variants (#7739)
- Forbid to edit the default variant of published extensions
- Hide the children configuration from the side panel when a variant is used
- Fix the Z-order of nested custom objects in the editor
- Fix a memory crash when updating an extension where behaviors must be removed from child-objects
2025-07-22 19:06:33 +02:00
github-actions[bot]
b74b221844 Update translations [skip ci] (#7730)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-07-22 11:43:45 +02:00
Clément Pasteau
38affc15b4 Fix making too many calls for courses (#7736)
Do not show in changelog
2025-07-22 11:43:21 +02:00
D8H
948488d92b [Top-down movement] Fix the legacy turning back mode (#7735) 2025-07-21 22:07:00 +02:00
Florian Rival
f5902d0346 Enable visibility of 3D cube backface by default 2025-07-21 10:52:41 +02:00
Clément Pasteau
f28dc8e88a Fix images pixelated because of border (#7732)
Do not show in changelog
2025-07-18 14:37:06 +02:00
Clément Pasteau
1f41749fa3 Fix carousel mobile (#7729)
Do not show in changelog
2025-07-17 15:49:36 +02:00
Florian Rival
a4908a4d42 Add spell check option for text input (disabled by default) (#7728) 2025-07-17 14:39:30 +02:00
Clément Pasteau
aa7754e658 Fixes responsive design and courses (#7726)
Do not show in changelog
2025-07-17 10:20:02 +02:00
github-actions[bot]
58ea9387aa Update extension translations [skip ci] (#7727)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-17 10:17:37 +02:00
github-actions[bot]
775266c974 Update translations [skip ci] (#7722)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-07-17 10:15:12 +02:00
Florian Rival
eb9794cd1f Bump newIDE version 2025-07-17 09:42:20 +02:00
Florian Rival
130732adde Refactor to make choice between chat or agent explicit
Don't show in changelog
2025-07-17 00:32:05 +02:00
Florian Rival
7a98e73d61 Fix AI not drawing properly multiple instances 2025-07-17 00:08:25 +02:00
D8H
1f26b72b4b Fix Physics3D from also creating a solid when the character behavior is re-activated (#7723) 2025-07-16 19:52:38 +02:00
D8H
a15ffb5b47 Add missing onSceneObjectsDeleted for custom object tabs (#7724) 2025-07-16 19:51:41 +02:00
Florian Rival
1a5f72283a Update price tag design 2025-07-16 18:36:25 +02:00
github-actions[bot]
0460b283ba Update translations [skip ci] (#7703)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-16 12:08:11 +02:00
Yaroslav Nazarenko
e212e7c780 Add an action to join a specific lobby using its identifier, and an expression to get the current lobby ID (#7694) 2025-07-16 12:07:32 +02:00
Clément Pasteau
84100fc7cf Introducing a new Learn page (#7705)
* The Get Started page has been removed and replaced by the Learn page as the first page displayed when GDevelop launches
  * The Learn page has been completely reworked to put forward the different resources a creator can use to improve their skills with Game Creation
  * A new option in the Preferences allows users to define the Create page as the default first page on launch
* Courses can now be purchased as a whole instead of per chapter, making it simpler to follow a full course
  * Bundles with multiple courses are coming up soon!
2025-07-16 11:59:56 +02:00
D8H
11a8682b07 Fix a crash of the scene editor when a custom object extension is updated (#7720) 2025-07-16 11:40:28 +02:00
Florian Rival
d3a0bbdfb1 Disable autorun of npm start when opening VSCode [skip ci] [ci skip] (#7719)
Only show in developer changelog
2025-07-16 10:56:09 +02:00
D8H
15f3a45d6a Fix 3D impulse and force toward a point actions (#7716) 2025-07-15 15:27:31 +02:00
D8H
f0a4f352cc Fix effect default values (#7706) 2025-07-15 13:56:20 +02:00
D8H
d16b3e8154 Fix multiplayer synchronization of custom object positions (#7715)
---------

Co-authored-by: Clément Pasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-15 13:38:23 +02:00
D8H
614fb97288 Fix 3D physics behaviors activation and deactivation (#7710) 2025-07-15 13:35:54 +02:00
D8H
8a40d3645a Fix the check box to stop sounds at scene start up not being applied properly (#7714) 2025-07-15 10:38:18 +02:00
D8H
2b7dadf2a8 Fix text input displayed position when inside a custom object whose layer is moved (#7713) 2025-07-14 17:47:44 +02:00
D8H
c338e16e4f Fix the "cursor is on" condition when used on custom object parent object (#7712) 2025-07-14 17:46:11 +02:00
Florian Rival
aded08471d Adapt sentences displaying free AI requests
Don't show in changelog
2025-07-13 19:40:10 +02:00
Florian Rival
cccb59b1c5 Fix AI agent not working with games with a lot of extensions or that were too big 2025-07-12 16:50:38 +02:00
D8H
3592fb7e62 Fix hemisphere light orientation when the top is set on Z+ (#7708) 2025-07-10 12:09:18 +02:00
D8H
307c92991c Fix shadow casting and receiving that were inverted for 3D boxes (#7704) 2025-07-09 13:43:01 +02:00
Florian Rival
4b3f077669 Bump newIDE version 2025-07-08 10:59:24 +02:00
github-actions[bot]
352bae518e Update translations [skip ci] (#7689)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-07-08 10:58:42 +02:00
D8H
c958f4d522 Fix directional light orientation and missing settings in the 3D cube editor (#7701) 2025-07-07 15:10:11 +02:00
D8H
35bbb37ad2 Fix hot-reloading of custom objects (#7698) 2025-07-07 08:37:08 +02:00
D8H
1d48acc841 Fix custom objects being destroyed too soon (#7695) 2025-07-07 08:27:00 +02:00
Florian Rival
87702edccc Show an highlight on newly generated AI events to make it easier to find them (#7696) 2025-07-06 19:20:41 +02:00
Florian Rival
1f0ba7c19a Update package.json 2025-07-04 16:45:43 +02:00
Florian Rival
b4d08a99ad Add setting to customize shadow bias 2025-07-04 16:44:55 +02:00
Florian Rival
8acaa06e42 Fix formatting [ci skip] [skip ci]
Don't show in changelog
2025-07-04 14:11:13 +02:00
Florian Rival
27ee85b5d4 Add shadow biais to avoid shadow acne
Don't show in changelog
2025-07-04 12:48:38 +02:00
Arthur Pacaud (arthuro555)
bbe2d1854e Update "Pick all instances" to avoid crashes when a lot of objects are picked (#7439) 2025-07-03 23:29:44 +02:00
Florian Rival
d338690ff5 Fix loading of 3D resources
This was a regresssion since the refactoring to allow unloading resources

Don't show in changelog
2025-07-03 17:56:30 +02:00
Florian Rival
571a6d8c1a Fix directional light settings not applied when scene starts
Don't show in changelog
2025-07-03 15:33:06 +02:00
Florian Rival
ddb5157c0a Bump newIDE version 2025-07-03 13:53:29 +02:00
Florian Rival
64f01354bc Fix formatting 2025-07-03 13:50:16 +02:00
Florian Rival
37fd99e542 Add user friendly labels to select fields in object and effect properties 2025-07-03 13:09:43 +02:00
Florian Rival
23be4a5849 Update shadow quality choices to be lowercase
Don't show in changelog
2025-07-03 12:34:57 +02:00
github-actions[bot]
64c0ee8f98 Update translations [skip ci] (#7683)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-07-03 12:23:19 +02:00
D8H
e5ecce3abf [Platformer] Fix wrongly allowed double jump when jumping from a slope (#7687) 2025-07-02 16:48:38 +02:00
ViktorVovk
5c71a4da56 Allow to unload scene resources when a scene is exited (#7381)
* This adds two new settings, available in the Project Properties and in the Scene Properties dialog, to allow to specify the strategy for preloading resources of the scenes and unloading them. By default, a game will preload in background the resources of all scenes. It will never unload these resources (so scene switching is fast).
* You can now choose to unload the resources of a scene when the scene is left using the "Resources unloading" field. If the scene is launched again later, it will load its resources again.
* You can also choose to change the preloading to disable it for all scenes (by modifying the setting in the project properties) or enable it/disable it on a scene by scene basis. This can be useful for large or modular games where you anticipate the player to only play some scenes, or if you want to reduce the resources that needs to be loaded on a web game.
2025-07-02 16:09:52 +02:00
Neyl
dff99b79cb Add basic support for shadows (#7592)
* Shadows are rendered for 3D objects when a **Directional Light** is set up on your scene layer - which is now the case by default for new games and new layers: they will have both a Directional Light and an Ambient Light. This renders shadows like it could be done by the sun.
* 3D models and 3D cubes are now casting shadows. To see them, you must ensure you the "Standard" material type in their configuration (and not the "Basic", which means they don't react to light) and be sure to enable "Shadow casting" and "Shadow receiving". This is done by default for new objects you create or import from the Asset Store.
* Shadows are rendered around the camera on an area that is large enough for most games while giving still good quality results. This means they should work out of the box and be adapted to most games, including large maps. 
  You can adapt the quality of shadows, intensity of the light (and so shadows), the size of the rendered area by editing the Effects of the layer in the scene (as for other effects).
2025-07-02 15:49:26 +02:00
github-actions[bot]
5fe46ea8ea Update extension translations [skip ci] (#7682)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-02 11:50:59 +02:00
Clément Pasteau
4a590adac4 Add new marketing specialization course (#7684) 2025-07-02 11:50:27 +02:00
Clément Pasteau
279d41cdb7 Fix resource selector text color on light theme (#7681) 2025-07-01 14:58:50 +02:00
github-actions[bot]
5cf65a9f62 Update translations [skip ci] (#7675)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-07-01 14:58:24 +02:00
D8H
08b05c13b6 Fix a crash when using the "Fixed rotation" action on a 3D character (#7680) 2025-06-30 15:24:14 +02:00
Aurélien Vivet
eb55c85f4e Fix "Wheel offset Z" and "Front wheel drive" properties of the 3D car behavior (#7678) 2025-06-30 15:23:41 +02:00
Florian Rival
8a243440db Improve some mathematical tools descriptions 2025-06-29 14:25:36 +02:00
Florian Rival
b3e4e6b89c Fix missing MassCenterZ expression for the 3D physics behavior 2025-06-28 16:35:37 +02:00
Florian Rival
a1a25f6df4 Bump newIDE version 2025-06-26 13:46:07 +02:00
Aurélien Vivet
6114a6cec1 Update the Create action with information about object picking (#7673) 2025-06-26 12:32:20 +02:00
github-actions[bot]
5058964937 Update translations [skip ci] (#7672)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-06-26 12:32:02 +02:00
Florian Rival
4488675540 Fix tutorials not always showing the element to scroll to for behaviors or extensions 2025-06-26 11:25:38 +02:00
D8H
6a2d2c9e67 Hide the behavior update dialog during in-app tutorial (#7674) 2025-06-26 11:02:55 +02:00
Florian Rival
b43c42d763 Fix broken tutorials 2025-06-25 18:50:58 +02:00
github-actions[bot]
69112183d4 Update translations [skip ci] (#7665)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-06-24 18:50:23 +02:00
Florian Rival
a4c2778b8d Fix keyboard avoidance on touchscreens for Ask AI tab (#7670)
* Fix keyboard avoidance on touchscreens for Ask AI tab
* Add placementId for in-app analytics

Don't show in changelog
2025-06-24 15:43:08 +02:00
D8H
f26e56c3bf Fix resource not loading when a custom object has both a variant and children overriding (#7668)
Don't show in changelog
2025-06-24 14:25:58 +02:00
Florian Rival
f5f9944fc4 Upgade m4pro.medium and XCode for CircleCI macOS build (#7669)
* This reduces total build time by ~40%.
2025-06-24 11:45:29 +02:00
D8H
9467caf1e9 Fix changing of variant not being applied at hot-reload (#7666) 2025-06-24 10:41:57 +02:00
Florian Rival
00376f39d5 Fix formatting and macOS CI build uploads
Don't show in changelog
2025-06-24 09:47:54 +02:00
Florian Rival
40b6a34dc5 Improve AI agent style
Don't show in changelog
2025-06-24 01:18:03 +02:00
Florian Rival
17d2b8c2c2 Improve the style of the text input for the AI agent/chat
Don't show in changelog
2025-06-23 23:41:29 +02:00
Florian Rival
935af42d23 Fix error when positioning instances in the AI agent
+ tentative fix for failing upload of macOS builds on the CI

Don't show in changelog
2025-06-23 20:05:26 +02:00
Florian Rival
d4a8d468cb Improve AI scene instance creation (#7667) 2025-06-23 18:52:39 +02:00
Florian Rival
b16099aee0 Bump newIDE version 2025-06-23 12:51:33 +02:00
github-actions[bot]
c17b918a43 Update translations [skip ci] (#7663)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-06-23 09:42:28 +02:00
D8H
d58e8c7ef9 Fix hot-reload when custom objects are in an external layout (#7664) 2025-06-21 13:51:37 +02:00
Florian Rival
ddd6b6e3a8 Open a new AI chat when clicking on button to try the AI agent 2025-06-21 12:22:54 +02:00
Florian Rival
e629c132ea Improve opening of scene and events tab for AI
Don't show in changelog
2025-06-20 18:01:44 +02:00
github-actions[bot]
b80e03f153 Update translations [skip ci] (#7660)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-06-20 15:46:09 +02:00
Florian Rival
11e36ff3f1 Fix CI not failing if macOS build upload failed 2025-06-20 15:16:03 +02:00
Florian Rival
22de356413 Improve animation names support for AI and add retry for macOS build deployment
Don't show in changelog
2025-06-20 12:56:46 +02:00
Florian Rival
caefa04fbe Give AI the names of the animations of animatable objects
Don't show in changelog
2025-06-20 11:53:13 +02:00
Florian Rival
cf2e7d67d7 Improve robustness of AI event generation
Don't show in changelog
2025-06-20 11:34:12 +02:00
D8H
685e444b2d Comply variants when an extension is updated for an asset (#7661)
- Don't show in changelog
2025-06-20 10:31:42 +02:00
Florian Rival
a9c1045afd Avoid extra scrollbar in dialog to create a project with AI
Don't show in changelog
2025-06-18 14:56:48 +02:00
Florian Rival
24e0d37583 Improve draggable behavior condition descriptions 2025-06-18 12:56:38 +02:00
Florian Rival
d44997d372 Fix AI agent unable to change boolean properties 2025-06-18 00:09:45 +02:00
Florian Rival
062aa888f8 Disable AI agent chat when started for another project
Don't show in changelog
2025-06-17 18:07:26 +02:00
github-actions[bot]
de4c2ae4ad Update translations (#7633)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-06-17 17:24:50 +02:00
Florian Rival
29ad7308c3 Introduce an experimental AI agent (#7659)
- This is an AI agent that takes a request and takes actions on a project: it can create scenes, find and create objects, add, remove behaviors, modify them, put instances on the scene, create or modify events, and more to come (layers, setup leaderboards, etc...).
- It's still in beta and there is room for improvement on many things, but is already useful for prototyping and learning - beginners notably are able to see what the AI can do and learn the concepts of GDevelop. For intermediate and power users, it's useful to try new things, or get things done while working on something else.
- Experiment with it and always make backup of your project before starting - in the future restoration points will be added to go back to a previous state if the result is not good or broken.
2025-06-17 16:25:03 +02:00
D8H
19b21c280e Fix a case where neither the variant nor the children are shown in the object editor (#7658)
- Don't show in changelog
2025-06-16 19:06:55 +02:00
Florian Rival
fbfe8b246a Fix broken scroll to current search result in the Events Sheet
Don't show in changelog
2025-06-15 20:51:42 +02:00
Florian Rival
73f66eb51f Fix flickering when switching between events editors (#7655)
- Thanks @necaTecnico for the investigation and first version of the fix
2025-06-12 15:47:13 +02:00
Florian Rival
d62ba2b9a0 Make the button to add an object more visible 2025-06-12 15:11:53 +02:00
D8H
323a2b6c2f [Top-down movement] Revert the modes (#7654) 2025-06-12 11:23:58 +02:00
D8H
8e4cccd562 Notice about extension breaking changes when installing assets (#7640)
- Add a pop-up to suggest to update behavior when attaching one to an object
- Keep the "community" label and the info button on installed extensions
2025-06-11 18:08:57 +02:00
D8H
795795ba40 Remove unused imports (#7651) 2025-06-11 14:08:54 +02:00
D8H
4af86b36e5 [Top-down movement] Turn back at least as fast as it decelerate (#7649)
- Add a new property that allows to choose between 3 modes
  - "Sharp turn with smooth turn back" which is what you are used to
  - "Sharp turn" which is more responsive
  - "Smooth turn" which keeps inertia
- Turn back at least as fast as it decelerate
  - You need to go in advanced and deprecated sections and uncheck the property to enable this in existing projects.
- Make analog stick controls more responsive
2025-06-11 11:11:59 +02:00
D8H
b00632a625 Remove big buttons to add objects and groups (#7650) 2025-06-11 10:25:57 +02:00
D8H
6f23f76441 [Platformer] Forbid repeated jumps while holding the jump key (#7648) 2025-06-09 19:14:49 +02:00
D8H
a6cd4b3c5d Fix missing behavior shared properties for global objects (#7647) 2025-06-09 13:37:23 +02:00
D8H
81d63c41b6 Fix panel sprite disappearing when left or top border is 0 (#7646)
- Don't show in changelog
2025-06-09 11:06:59 +02:00
Florian Rival
a924840228 Fix local variables sometimes briefly shown as invalid in the Events Sheet 2025-06-09 10:51:11 +02:00
Florian Rival
b013297c8e Fix events rendering regression (some sub-events not visible when expanded)
Don't shown in changelog
2025-06-09 10:00:23 +02:00
Florian Rival
ca77a31037 Use a CDN and updated endpoints to load the list of examples
Only show in developer changelog
2025-06-06 17:06:35 +02:00
Florian Rival
5adb2240d5 Use a CDN and updated endpoints to load extensions and behaviors (#7644)
Only show in developer changelog
2025-06-06 13:59:52 +02:00
D8H
9d42be3362 Fix panel sprite rendering when the size is smaller than margins (#7643)
* The borders will be shrunk proportionally to their size.
2025-06-06 13:59:02 +02:00
Dima Lifanchuk
21201dec29 Extract playback sound logic in separate methods (#7641)
Only show in developer changelog
2025-06-05 17:52:16 +02:00
Aurélien Vivet
08229cbe1d Fix typo (#7639) 2025-06-05 15:07:04 +02:00
Florian Rival
96e9dd7c4b Update Tween description
Don't show in changelog
2025-06-04 19:46:14 +02:00
Aurélien Vivet
7dbc687200 Fix spacing in the documentation (#7638)
Don't show in changelog
2025-06-04 17:30:22 +02:00
Florian Rival
7e1f2c6c97 Add support search (and replace) in Link events (#7637) 2025-06-04 16:42:26 +02:00
Aurélien Vivet
37cba12e4a Add a mention in the reference pages of extensions telling no installation is required (#7635) 2025-06-04 15:35:49 +02:00
Florian Rival
cdd80bca9e Build and sign the Windows build with CircleCI (#7630)
* Code signing is somehow broken on AppVeyor. Keep AppVeyor in case it works again (but deployment is disabled)
* Use a "medium" worker for Windows as it's long a few minutes longer than "large" and 3x less expensive.
2025-06-03 19:40:08 +02:00
Florian Rival
3293d24c36 Remove roundtrip to the API by using CDN directly for public assets 2025-06-03 15:43:55 +02:00
Florian Rival
bf31781d7a Lazily load subscription plans to reduce requests at startup (#7631) 2025-06-03 14:16:02 +02:00
Florian Rival
f6c43b2db3 Improve description of the 2D particle emitter 2025-05-30 17:17:57 +02:00
Florian Rival
f00156a654 Improve "Destroy Outside" behavior descriptions 2025-05-30 16:52:17 +02:00
Florian Rival
41fd1cbcee Make Events Sheet more robust against changes in events made outside of the tab (#7625)
Only show in developer changelog
2025-05-29 18:17:14 +02:00
Florian Rival
52c807d74a Use separately built GDevelop.js for Windows and Linux builds (#7627)
Only show in developer changelog
2025-05-28 18:51:10 +02:00
D8H
8713a496b4 Declare the assets dimensions according to their variants in exported GDO (#7623) 2025-05-27 19:30:25 +02:00
Clément Pasteau
a3033f2db1 Update badge link to suggest opening profile after first click (#7624) 2025-05-27 10:59:57 +02:00
Florian Rival
5fb8faffc1 Improve Particle emitter description
Don't show in changelog
2025-05-25 17:02:02 +02:00
Clément Pasteau
a883955703 Add the Premium course to the Education curriculum (#7619) 2025-05-22 15:38:29 +02:00
Clément Pasteau
b39d7adcbc Fix displaying Teach tab if no members (#7617)
Do not show in changelog
2025-05-20 17:19:24 +02:00
github-actions[bot]
c577c8db71 Update extension translations [skip ci] (#7615)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-05-16 15:53:02 +02:00
Clément Pasteau
d7d17400dd Fix error accessing deleted objects after an object is deleted from a scene (#7612) 2025-05-15 18:18:09 +02:00
D8H
b219d50fd8 Fix a typo in the "hand brake" action (#7613)
- Don't show in changelog
2025-05-15 16:48:30 +02:00
Clément Pasteau
88f318e6df Bump to 5.5.231 (#7610)
Do not show in changelog
2025-05-15 11:03:54 +02:00
github-actions[bot]
d12ac44b7d Update translations [skip ci] (#7611)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-05-15 11:03:32 +02:00
Clément Pasteau
7155eea716 Show gd.games homepage on play section by default on mobile (#7609) 2025-05-15 10:46:49 +02:00
github-actions[bot]
ca0796f131 Update translations [skip ci] (#7595)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-05-15 10:35:56 +02:00
Clément Pasteau
c4b33e2481 Prevent continuing to use GDevelop if not all extensions have been loaded (#7608)
* GDevelop sometimes has faulty updates, for instance when the computer is shut down during an update. This was causing the app to be incomplete and projects to become corrupted after a save (ex: BBText objects being broken)
* This check shows an unskippable dialog suggesting a re-install of GDevelop
2025-05-15 10:15:42 +02:00
D8H
ff95564b6b Add new 3D physics-based vehicle behavior (#7479)
* You can now simulate realistic 3D cars using the new Vehicle behavior powered by the 3D physics engine.
* Advanced details can be customized: from wheel setup and gear ratios to engine torque, steering, brakes, anti-roll bars, and drivetrain (front/rear).
* The default setup offers a semi-realistic, arcade-style driving feel. For smoother gameplay, explore the example projects to see how fine-tuning makes a big difference.
* Jolt physics engine was upgraded to version 0.34.0.
2025-05-14 10:52:47 +02:00
Clément Pasteau
cecf1ab791 Fix crash when selecting a resource after switching tabs in the Resource selection dialog (#7607) 2025-05-14 10:28:12 +02:00
D8H
a571445e0e Allow custom objects to self-destruct (#7605) 2025-05-12 14:03:21 +02:00
D8H
89e418cd24 Fix grid snapping of pasted instances (#7602) 2025-05-12 11:41:42 +02:00
Florian Rival
896ccfcffa Add hint that mapper behaviors should be used for platformer (#7601)
Don't show in changelog
2025-05-12 10:51:09 +02:00
Clément Pasteau
d98d181755 Fix typo (#7604)
Do not show in changelog
2025-05-12 10:30:08 +02:00
Florian Rival
3c63f9b617 Update 3D extension internal descriptions
Only show in developer changelog
2025-05-08 12:26:54 +02:00
D8H
8312bbe4f8 Fix 3D Physics "apply force/impulse toward position" action (#7600) 2025-05-06 21:04:02 +02:00
Clément Pasteau
bb77a71f26 Bump to 5.5.230 (#7593)
Do not show in changelog
2025-05-06 10:10:48 +02:00
D8H
4353469554 Fix bitmap text alignment and rotation (#7597)
* Fix BBText rotation
2025-05-05 20:10:21 +02:00
Clément Pasteau
46ea431f62 Fix firebase tests (#7594)
Do not show in changelog
2025-05-05 15:16:39 +02:00
github-actions[bot]
2d29d43355 Update translations [skip ci] (#7577)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-05-05 11:22:08 +02:00
Clément Pasteau
28a4e253a1 Update desktop & web icons with latest GDevelop branding (#7578) 2025-05-05 10:22:40 +02:00
D8H
f3dcb8eec8 Fix asset export for objects with unused children overriding (#7590) 2025-05-05 09:53:11 +02:00
Florian Rival
bf9e38ff31 Improve descriptions of boolean logic conditions
Only show in developer changelog
2025-05-04 16:08:36 +02:00
D8H
87649b9def Ensure variants downloaded from the store comply to their events-based object (#7587) 2025-05-02 12:51:58 +02:00
D8H
60d332e872 Fix rendered custom objects in the editor with an overriding of children configuration (#7585)
- Fix children overriding to only apply to the default variant at runtime
2025-05-02 12:51:14 +02:00
D8H
bdab12b1e6 Fix variant edition: forbid to edit variants from the store (they should be duplicated instead) (#7586) 2025-05-02 12:05:49 +02:00
Florian Rival
02f795f2c1 Update AI requests wording 2025-04-29 09:52:19 +02:00
AlexandreS
6e64d8521f Submit ai question with Ctrl+Enter or Go to button of mobile soft keyboard (#7579)
Don't show in changelog
2025-04-28 18:51:39 +02:00
Clément Pasteau
0e9aea1c9d Fix showing Checkered background on sprite project resources (#7580)
Do not show in changelog
2025-04-28 18:31:24 +02:00
Clément Pasteau
99901bf539 Bump newIDE version (#7576) 2025-04-28 12:07:23 +02:00
github-actions[bot]
53f1d745f5 Update translations [skip ci] (#7572)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-28 10:28:31 +02:00
D8H
b72034a475 Fix the sentence parameter of "loading progress" condition (#7574) 2025-04-28 10:27:04 +02:00
AlexandreS
926f6a2c56 Remove useless param when changing user subscription (#7573)
Don't show in changelog
2025-04-25 17:20:23 +02:00
D8H
32cc6a3109 Fix an anchor behavior test (#7571)
- Don't show in changelogs
2025-04-25 11:04:48 +02:00
github-actions[bot]
f9ab65155d Update translations [skip ci] (#7561)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-25 10:57:40 +02:00
github-actions[bot]
d700a9d26d Update extension translations [skip ci] (#7565)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-25 10:57:08 +02:00
Florian Rival
297b88ed60 Improve description of the condition to compare a timer value 2025-04-24 17:14:52 +02:00
Aurélien Vivet
06cef654b0 Add explanatory tooltip to game analytics graph (#7559) 2025-04-24 15:17:14 +02:00
D8H
c5dd26c93b Remove another unused import (#7568)
Don't show in changelog
2025-04-24 12:28:21 +02:00
D8H
e0f3b221bd Remove unused import (#7567)
Don't show in changelog
2025-04-24 12:07:43 +02:00
D8H
896f56e850 Allow to switch between variants of custom objects (#7403)
- Variants allows to restyle custom objects
- They can be customized with the graphical editor
- The asset store will progressively use them notably for UI elements (buttons, sliders)
2025-04-24 11:05:00 +02:00
Aurélien Vivet
cee43ce9df Fix typo in GuidedLessons.js (#7566)
Do not show in changelog
2025-04-24 10:11:56 +02:00
D8H
5e61712e55 Fix various issues with the 3D physics engine (#7564)
* Fix the box shape to always be oriented on Z
* Fix memory leaks
2025-04-23 19:55:06 +02:00
Clément Pasteau
b3fa34ce3c Typo in description (#7560)
Do not show in changelog
2025-04-22 10:14:19 +02:00
github-actions[bot]
1bdb4c0369 Update extension translations [skip ci] (#7558)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-18 18:21:21 +02:00
github-actions[bot]
2822fab5ed Update translations [skip ci] (#7553)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2025-04-18 18:20:58 +02:00
AlexandreS
658ac381fb Send language when requesting chapters when not authenticated (#7557)
Don't show in changelog
2025-04-18 17:10:19 +02:00
D8H
80cf54cb1b Fix anchored object position when the object and the camera is moved (#7556) 2025-04-18 12:50:43 +02:00
AlexandreS
2f56f6b715 Add new course about UI/UX essentials (#7555)
Keep learning with GDevelop with this new course about UI/UX essentials to make sure your game delivers the best experience to your players!
2025-04-18 11:13:04 +02:00
Clément Pasteau
9784113574 Fix allowing resource swap in resource editor on web (#7554)
Do not show in changelog
2025-04-16 17:59:54 +02:00
Clément Pasteau
e2de3bec34 Update worker url logic + consistent lights + fix using in resources list (#7551)
Do not show in changelog
2025-04-16 16:27:24 +02:00
github-actions[bot]
e837df4882 Update translations [skip ci] (#7550)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-15 16:13:22 +02:00
Clément Pasteau
2f44dab18b 3D models can now be previewed (#7547)
* Screenshots are taken for each 3D Model (using .glb extension) so they can be easily recognized in the editor
* Those previews are visible in the Resources tab, in the 3D Model object dialog, or when swapping/selecting from a project resource
2025-04-15 16:02:55 +02:00
Aurélien Vivet
10049ce42a Add a button to copy the Ask AI response (#7548) 2025-04-15 15:59:32 +02:00
D8H
8d9a60f819 Use a shorter label for the button to toggle parameters in drop-down list (#7549) 2025-04-15 15:58:38 +02:00
Florian Rival
8ea8c421b2 Fix TextContainerExtension description 2025-04-14 18:42:03 +02:00
github-actions[bot]
f7b026f1cc Update translations [skip ci] (#7544)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-14 17:27:25 +02:00
Florian Rival
623535f7fd Revert test timemout (not supported by CRA)
Don't show in changelog
2025-04-14 09:20:28 +02:00
Florian Rival
3f0ff4a9de Increase default timeout for IDE tests
Avoid failing an AppVeyor Windows build because of a timeout
2025-04-11 14:08:53 +02:00
Clément Pasteau
883ca6d535 Allow selecting resources that are already in the project when editing an object (#7541) 2025-04-11 12:21:06 +02:00
github-actions[bot]
77f56829b3 Update translations [skip ci] (#7537)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-08 10:36:24 +02:00
Clément Pasteau
12a842e197 Fix Piskel crashing with black screen when doing multiple undos (#7539) 2025-04-08 09:33:16 +02:00
Clément Pasteau
7145e6d049 Hide Ask AI for student accounts (#7533)
Do not show in changelog
2025-04-07 11:10:52 +02:00
László Nyiri
caa18e5fcb Fix "Never reached character" error during GDevelop.js build (#7536)
Only show in developer changelog
2025-04-07 09:20:26 +02:00
github-actions[bot]
61bf8a7cab Update translations [skip ci] (#7529)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-04 11:32:48 +02:00
Clément Pasteau
98b3687157 Fix cmake version for mac builds (#7531)
Do not show in changelog
2025-04-04 11:20:20 +02:00
github-actions[bot]
4b4fba2c7a Update extension translations [skip ci] (#7532)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-04 11:18:44 +02:00
Clément Pasteau
cedb9429c7 Improve languages dialog display (#7528)
Do not show in changelog
2025-04-02 11:28:27 +02:00
github-actions[bot]
d844b4d380 Update extension translations [skip ci] (#7527)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-02 10:07:12 +02:00
github-actions[bot]
e4265553e0 Update translations [skip ci] (#7526)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-02 09:13:15 +02:00
Florian Rival
2524b0b9d2 Update wording
Don't show in changelog
2025-04-01 18:02:37 +02:00
Florian Rival
20d16b8a57 Add minor improvements to Ask AI
Don't show in changelog
2025-04-01 13:26:14 +02:00
github-actions[bot]
67aa1ce062 Update translations [skip ci] (#7524)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-01 10:37:54 +02:00
Florian Rival
1759dda870 Fix formatting 2025-04-01 00:21:01 +02:00
Florian Rival
03dce1d90a Add dialog to report AI message feedback 2025-04-01 00:16:31 +02:00
Florian Rival
4e9556e948 Fix tests and improve wording of tilemaps 2025-03-31 22:53:07 +02:00
D8H
a02b8dcfe0 Add vertical alignement for BitmapText and BBText (#7523) 2025-03-31 21:00:58 +02:00
Florian Rival
416ef44ee1 Improve Ask AI tab
Don't show in changelog
2025-03-31 18:46:48 +02:00
github-actions[bot]
aa90621899 Update translations [skip ci] (#7516)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-03-31 11:14:33 +02:00
github-actions[bot]
3d3f04f63e Update extension translations [skip ci] (#7521)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-03-31 10:42:15 +02:00
988 changed files with 100411 additions and 25690 deletions

View File

@@ -13,17 +13,18 @@ orbs:
aws-cli: circleci/aws-cli@2.0.6
macos: circleci/macos@2.5.1 # For Rosetta (see below)
node: circleci/node@5.2.0 # For a recent npm version (see below)
win: circleci/windows@5.1.0
jobs:
# Build the **entire** app for macOS.
# Build the **entire** app for macOS (including the GDevelop.js library).
build-macos:
macos:
xcode: 14.2.0
resource_class: macos.m1.large.gen1
xcode: 16.4.0
resource_class: m4pro.medium
steps:
- checkout
# Install Rosetta for AWS CLI and disable TSO to speed up S3 uploads (https://support.circleci.com/hc/en-us/articles/19334402064027-Troubleshooting-slow-uploads-to-S3-for-jobs-using-an-m1-macOS-resource-class)
- macos/install-rosetta
- run: sudo sysctl net.inet.tcp.tso=0
# - run: sudo sysctl net.inet.tcp.tso=0
# Install a recent version of npm to workaround a notarization issue because of a symlink made by npm: https://github.com/electron-userland/electron-builder/issues/7755
# Node.js v20.14.0 comes with npm v10.7.0.
@@ -46,9 +47,9 @@ jobs:
# GDevelop.js dependencies
- restore_cache:
keys:
- gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
- gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
# fallback to using the latest cache if no exact match is found
- gd-macos-nodejs-dependencies---
- gd-macos-nodejs-dependencies-
- run:
name: Install GDevelop.js dependencies
@@ -69,7 +70,8 @@ jobs:
- newIDE/electron-app/node_modules
- newIDE/app/node_modules
- GDevelop.js/node_modules
key: gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
- GDJS/node_modules
key: gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
# Note: Code signing is done using CSC_LINK (see https://www.electron.build/code-signing).
@@ -86,15 +88,37 @@ jobs:
- store_artifacts:
path: newIDE/electron-app/dist
# Upload artifacts (AWS)
- run:
name: Deploy to S3 (specific commit)
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/
command: |
export PATH=~/.local/bin:$PATH
for i in 1 2 3 4 5 6 7; do
aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/ && break
echo "Retry $i failed... retrying in 10 seconds"
sleep 10
done
if [ $i -eq 7 ]; then
echo "All retries for deployment failed!" >&2
exit 1
fi
- run:
name: Deploy to S3 (latest)
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/
command: |
export PATH=~/.local/bin:$PATH
for i in 1 2 3 4 5 6 7; do
aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/ && break
echo "Retry $i failed... retrying in 10 seconds"
sleep 10
done
if [ $i -eq 7 ]; then
echo "All retries for deployment failed!" >&2
exit 1
fi
# Build the **entire** app for Linux.
# Build the app for Linux (using a pre-built GDevelop.js library).
build-linux:
# CircleCI docker workers are failing if they don't have enough memory (no swap)
resource_class: xlarge
@@ -107,51 +131,33 @@ jobs:
- checkout
- aws-cli/setup
# System dependencies (for Electron Builder and Emscripten)
# System dependencies (for Electron Builder)
- run:
name: Install dependencies for Emscripten
command: sudo apt-get update && sudo apt install cmake
- run:
name: Install Python3 dependencies for Emscripten
command: sudo apt install python-is-python3 python3-distutils -y
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
name: Update system dependencies
command: sudo apt-get update
- run:
name: Install system dependencies for Electron builder
command: sudo apt install icnsutils && sudo apt install graphicsmagick && sudo apt install rsync
# GDevelop.js dependencies
- restore_cache:
keys:
- gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
- gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
# fallback to using the latest cache if no exact match is found
- gd-linux-nodejs-dependencies---
- gd-linux-nodejs-dependencies-
- run:
name: Install GDevelop.js dependencies and build it
command: cd GDevelop.js && npm install && cd ..
# Build GDevelop.js (and run tests to ensure it works)
- run:
name: Build GDevelop.js
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
# GDevelop IDE dependencies (after building GDevelop.js to avoid downloading a pre-built version)
# GDevelop IDE dependencies (using an exact version of GDevelop.js, built previously)
- run:
name: Install GDevelop IDE dependencies
command: cd newIDE/app && npm install && cd ../electron-app && npm install
command: export REQUIRES_EXACT_LIBGD_JS_VERSION=true && cd newIDE/app && npm install && cd ../electron-app && npm install
- save_cache:
paths:
- newIDE/electron-app/node_modules
- newIDE/app/node_modules
- GDevelop.js/node_modules
key: gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
- GDJS/node_modules
key: gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
- run:
@@ -295,14 +301,203 @@ jobs:
name: Deploy to S3 (specific commit)
command: aws s3 sync Binaries/embuild/GDevelop.js s3://gdevelop-gdevelop.js/$(git rev-parse --abbrev-ref HEAD)/variant/debug-sanitizers/commit/$(git rev-parse HEAD)/
# Trigger AppVeyor build, which also does a Windows build (keep it for redundancy).
trigger-appveyor-windows-build:
docker:
- image: cimg/node:16.13
steps:
- run:
name: Trigger AppVeyor Windows build
command: |
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer ${APPVEYOR_API_KEY}" \
--data "{
\"accountName\": \"4ian\",
\"projectSlug\": \"gdevelop\",
\"branch\": \"${CIRCLE_BRANCH}\"
}" \
-X POST https://ci.appveyor.com/api/builds
build-windows:
executor:
name: win/default
size: medium
working_directory: /home/circleci/project
steps:
- checkout
- run:
# See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/
#
# This is necessary because of "signing to be FIPS-140 compliant". See
# https://github.com/electron-userland/electron-builder/issues/6158
#
# Make sure to DISABLE "malware blocker" in SSL.com to avoid errors like:
# Error information: "Error: SignerSign() failed." (-2146893821/0x80090003)
name: Download and Unzip eSignerCKA Setup
command: |
Invoke-WebRequest -OutFile eSigner_CKA_1.0.3.zip "https://www.ssl.com/download/ssl-com-esigner-cka-1-0-3"
Expand-Archive -Force eSigner_CKA_1.0.3.zip
Remove-Item eSigner_CKA_1.0.3.zip
Move-Item -Destination "eSigner_CKA_1.0.3.exe" -Path "eSigner_CKA_*\*.exe"
- run:
name: Setup eSignerCKA in Silent Mode
command: |
mkdir -p "/home/circleci/project/eSignerCKA"
./eSigner_CKA_1.0.3.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR="/home/circleci/project/eSignerCKA" | Out-Null
- run:
name: Config Account Information on eSignerCKA
command: |
/home/circleci/project/eSignerCKA/eSignerCKATool.exe config -mode product -user "$env:ESIGNER_USER_NAME" -pass "$env:ESIGNER_USER_PASSWORD" -totp "$env:ESIGNER_USER_TOTP" -key "/home/circleci/project/eSignerCKA/master.key" -r
- run:
name: Load Certificate into Windows Store
command: |
/home/circleci/project/eSignerCKA/eSignerCKATool.exe unload
/home/circleci/project/eSignerCKA/eSignerCKATool.exe load
- run:
name: Select Certificate From Windows Store and Sign Sample File with SignTool
command: |
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
echo Certificate: $CodeSigningCert
- restore_cache:
name: Restore node_modules cache
keys:
- v1-win-node-{{ checksum "newIDE/app/package-lock.json" }}-{{ checksum "newIDE/electron-app/package-lock.json" }}-{{ checksum "GDJS/package-lock.json" }}
- v1-win-node-
- run:
name: Install dependencies
no_output_timeout: 25m
# Remove package-lock.json because they seems to cause the npm install to be stuck. We should try again after re-generating them.
# Also install setuptools as something requires distutils in electron-app, and it was removed in Python 3.12.
# setuptools will make distutils available again (but we should migrate our packages probably).
command: |
pip install setuptools
cd newIDE\app
npm -v
Remove-Item package-lock.json
$Env:REQUIRES_EXACT_LIBGD_JS_VERSION = "true"
npm install
cd ..\electron-app
Remove-Item package-lock.json
npm install
cd ..\..
- save_cache:
name: Save node_modules cache
key: v1-win-node-{{ checksum "newIDE/app/package-lock.json" }}-{{ checksum "newIDE/electron-app/package-lock.json" }}-{{ checksum "GDJS/package-lock.json" }}
paths:
- newIDE/app/node_modules
- newIDE/electron-app/node_modules
- GDJS/node_modules
- run:
name: Build NSIS executable (with code signing)
command: |
cd newIDE\electron-app
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
echo Certificate: $CodeSigningCert
# Use a custom signtool path because of the signtool.exe bundled withy electron-builder not working for some reason.
# Can also be found in versioned folders like "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe".
# or "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\signtool.exe".
$Env:SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe"
# Extract thumbprint and subject name of the certificate (will be passed to electron-builder).
$Env:GD_SIGNTOOL_THUMBPRINT = $CodeSigningCert.Thumbprint
$Env:GD_SIGNTOOL_SUBJECT_NAME = ($CodeSigningCert.Subject -replace ", ?", "`n" | ConvertFrom-StringData).CN
# Build the nsis installer (signed: electron-builder will use SignTool.exe with the certificate)
node scripts/build.js --win nsis --publish=never
cd ..\..
- run:
name: Build AppX (without code signing)
# Don't sign the appx (it will be signed by the Microsoft Store).
command: |
cd newIDE\electron-app
# Build the appx (not signed). Ensure all variables used for code signing are empty.
$Env:GD_SIGNTOOL_THUMBPRINT = ''
$Env:GD_SIGNTOOL_SUBJECT_NAME = ''
$Env:CSC_LINK = ''
$Env:CSC_KEY_PASSWORD = ''
node scripts/build.js --skip-app-build --win appx --publish=never
cd ..\..
- run:
name: Clean binaries
shell: cmd.exe
command: |
rmdir /s /q newIDE\electron-app\dist\win-unpacked
- run:
name: Install AWS CLI
command: |
# Install the CLI for the current user
pip install --quiet --upgrade --user awscli
# Add the user-Scripts dir to PATH for this step and the next.
$binDir = (python -m site --user-base) + "\Scripts"
$Env:Path += ";$binDir"
# Sanity check:
aws --version
# Upload artifacts (S3)
- run:
name: Deploy to S3 (specific commit)
command: |
aws s3 sync newIDE\electron-app\dist "s3://gdevelop-releases/$Env:CIRCLE_BRANCH/commit/$Env:CIRCLE_SHA1/"
- run:
name: Deploy to S3 (latest)
command: |
aws s3 sync newIDE\electron-app\dist "s3://gdevelop-releases/$Env:CIRCLE_BRANCH/latest/"
# Upload artifacts (CircleCI)
- store_artifacts:
path: newIDE/electron-app/dist
workflows:
gdevelop_js-wasm:
jobs:
- build-gdevelop_js-wasm-only
gdevelop_js-wasm-extra-checks:
jobs:
- build-gdevelop_js-debug-sanitizers-and-extra-checks:
# Extra checks are resource intensive so don't all run them.
# Extra checks are resource intensive so don't always run them.
filters:
branches:
only:
@@ -310,13 +505,36 @@ workflows:
- /experimental-build.*/
builds:
jobs:
- build-gdevelop_js-wasm-only
- build-macos:
# The macOS version builds by itself GDevelop.js
# (so we verify we can build it on macOS).
# requires:
# - build-gdevelop_js-wasm-only
filters:
branches:
only:
- master
- /experimental-build.*/
- build-linux:
requires:
- build-gdevelop_js-wasm-only
filters:
branches:
only:
- master
- /experimental-build.*/
- build-windows:
requires:
- build-gdevelop_js-wasm-only
filters:
branches:
only:
- master
- /experimental-build.*/
- trigger-appveyor-windows-build:
requires:
- build-gdevelop_js-wasm-only
filters:
branches:
only:

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@
.Spotlight-V100
.Trashes
Thumbs.db
.claude

3
.vscode/tasks.json vendored
View File

@@ -38,8 +38,7 @@
"presentation": {
"reveal": "silent"
},
"isBackground": true,
"runOptions": { "instanceLimit": 1, "runOn": "folderOpen" }
"isBackground": true
},
{
"type": "npm",

View File

@@ -61,10 +61,12 @@ void GroupEvent::UnserializeFrom(gd::Project& project,
project, events, element.GetChild("events"));
parameters.clear();
gd::SerializerElement& parametersElement = element.GetChild("parameters");
parametersElement.ConsiderAsArrayOf("parameters");
for (std::size_t i = 0; i < parametersElement.GetChildrenCount(); ++i)
parameters.push_back(parametersElement.GetChild(i).GetValue().GetString());
if (element.HasChild("parameters")) {
gd::SerializerElement& parametersElement = element.GetChild("parameters");
parametersElement.ConsiderAsArrayOf("parameters");
for (std::size_t i = 0; i < parametersElement.GetChildrenCount(); ++i)
parameters.push_back(parametersElement.GetChild(i).GetValue().GetString());
}
}
void GroupEvent::SetBackgroundColor(unsigned int colorR_,

View File

@@ -163,6 +163,21 @@ void LinkEvent::UnserializeFrom(gd::Project& project,
// end of compatibility code
}
vector<gd::String> LinkEvent::GetAllSearchableStrings() const {
vector<gd::String> allSearchableStrings;
allSearchableStrings.push_back(target);
return allSearchableStrings;
}
bool LinkEvent::ReplaceAllSearchableStrings(
std::vector<gd::String> newSearchableString) {
if (newSearchableString[0] == target) return false;
SetTarget(newSearchableString[0]);
return true;
}
bool LinkEvent::AcceptVisitor(gd::EventVisitor &eventVisitor) {
return BaseEvent::AcceptVisitor(eventVisitor) ||
eventVisitor.VisitLinkEvent(*this);

View File

@@ -109,6 +109,10 @@ class GD_CORE_API LinkEvent : public gd::BaseEvent {
virtual bool IsExecutable() const override { return true; };
virtual std::vector<gd::String> GetAllSearchableStrings() const override;
virtual bool ReplaceAllSearchableStrings(
std::vector<gd::String> newSearchableString) override;
virtual void SerializeTo(SerializerElement& element) const override;
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element) override;

View File

@@ -42,15 +42,19 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
const vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t relationalOperatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
std::size_t relationalOperatorIndex =
instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "relationalOperator") {
if (instrInfos.parameters.GetParameter(i).GetType() ==
"relationalOperator") {
relationalOperatorIndex = i;
}
}
// Ensure that there is at least one parameter after the relational operator
if (relationalOperatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
if (relationalOperatorIndex + 1 >=
instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -87,20 +91,23 @@ gd::String EventsCodeGenerator::GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) {
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " + rhs;
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " +
rhs;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=" &&
operatorString != "startsWith" && operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(
const gd::String& operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" &&
operatorString != "!=" && operatorString != "startsWith" &&
operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
/**
@@ -124,7 +131,8 @@ gd::String EventsCodeGenerator::GenerateOperatorCall(
const gd::String& getterStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -195,7 +203,8 @@ gd::String EventsCodeGenerator::GenerateCompoundOperatorCall(
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -248,7 +257,8 @@ gd::String EventsCodeGenerator::GenerateMutatorCall(
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -323,83 +333,97 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
// Insert code only parameters and be sure there is no lack of parameter.
while (condition.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
while (condition.GetParameters().size() <
instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = condition.GetParameters();
parameters.push_back(gd::Expression(""));
condition.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
++pNb) {
if (ParameterMetadata::IsObject(
instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter =
condition.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
const auto& expectedObjectType =
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
const auto& actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
objectInParameter)) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
objectInParameter, "");
gd::ProjectDiagnostic::ErrorType::UnknownObject,
"",
objectInParameter,
"");
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Unknown object - skipped. */";
} else if (!expectedObjectType.empty() &&
actualObjectType != expectedObjectType) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
actualObjectType, expectedObjectType, objectInParameter);
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
"",
actualObjectType,
expectedObjectType,
objectInParameter);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Mismatched object type - skipped. */";
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
if (instrInfos.IsObjectInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
if (!objectName.empty() && instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Set up the context
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
gd::String objectType =
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
// Prepare arguments and generate the condition whole code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateObjectCondition(realObjects[i],
objInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
// Prepare arguments and generate the condition whole code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateObjectCondition(realObjects[i],
objInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
context.SetNoCurrentObject();
context.SetNoCurrentObject();
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = condition.GetParameter(0).GetPlainString();
const gd::String &behaviorName =
const gd::String& objectName = condition.GetParameter(0).GetPlainString();
const gd::String& behaviorName =
condition.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
const BehaviorMetadata &autoInfo =
const BehaviorMetadata& autoInfo =
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
for (std::size_t i = 0; i < realObjects.size(); ++i) {
@@ -411,15 +435,14 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
// Prepare arguments and generate the whole condition code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateBehaviorCondition(
realObjects[i],
behaviorName,
autoInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
conditionCode += GenerateBehaviorCondition(realObjects[i],
behaviorName,
autoInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
context.SetNoCurrentObject();
}
@@ -488,31 +511,50 @@ gd::String EventsCodeGenerator::GenerateConditionsListCode(
return outputCode;
}
void EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos) {
gd::ParameterMetadataTools::IterateOverParameters(
instruction.GetParameters(), instrInfos.parameters,
[this](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue,
const gd::String &lastObjectName) {
bool EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction& instruction,
const gd::InstructionMetadata& instrInfos) {
bool isAnyBehaviorMissing = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
instrInfos.parameters,
[this, &isAnyBehaviorMissing, &instrInfos](
const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName,
size_t lastObjectIndex) {
if (ParameterMetadata::IsBehavior(parameterMetadata.GetType())) {
const gd::String &behaviorName = parameterValue.GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& behaviorName = parameterValue.GetPlainString();
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehaviorInObjectOrGroup(
lastObjectName, behaviorName);
const gd::String &expectedBehaviorType =
const gd::String& expectedBehaviorType =
parameterMetadata.GetExtraInfo();
if (!expectedBehaviorType.empty() &&
actualBehaviorType != expectedBehaviorType) {
const auto& objectParameterMetadata =
instrInfos.GetParameter(lastObjectIndex);
// Event functions crash if some objects in a group are missing
// the required behaviors, since they lose reference to the original
// objects. Missing behaviors are considered "fatal" only for
// ObjectList parameters, in order to minimize side effects on
// built-in functions.
if (objectParameterMetadata.GetType() == "objectList") {
isAnyBehaviorMissing = true;
}
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MissingBehavior, "",
actualBehaviorType, expectedBehaviorType, lastObjectName);
gd::ProjectDiagnostic::ErrorType::MissingBehavior,
"",
actualBehaviorType,
expectedBehaviorType,
lastObjectName);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
}
}
});
return isAnyBehaviorMissing;
}
/**
@@ -521,7 +563,8 @@ void EventsCodeGenerator::CheckBehaviorParameters(
gd::String EventsCodeGenerator::GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
gd::String actionCode;
const gd::InstructionMetadata& instrInfos =
@@ -546,39 +589,51 @@ gd::String EventsCodeGenerator::GenerateActionCode(
: instrInfos.codeExtraInformation.functionCallName;
// Be sure there is no lack of parameter.
while (action.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
while (action.GetParameters().size() <
instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = action.GetParameters();
parameters.push_back(gd::Expression(""));
action.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
++pNb) {
if (ParameterMetadata::IsObject(
instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
const auto& expectedObjectType =
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
const auto& actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
objectInParameter)) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
objectInParameter, "");
gd::ProjectDiagnostic::ErrorType::UnknownObject,
"",
objectInParameter,
"");
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Unknown object - skipped. */";
} else if (!expectedObjectType.empty() &&
actualObjectType != expectedObjectType) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
actualObjectType, expectedObjectType, objectInParameter);
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
"",
actualObjectType,
expectedObjectType,
objectInParameter);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Mismatched object type - skipped. */";
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
// Call free function first if available
if (instrInfos.IsObjectInstruction()) {
@@ -586,43 +641,46 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
gd::String objectType =
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName,
optionalAsyncCallbackId);
context.SetNoCurrentObject();
context.SetNoCurrentObject();
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = action.GetParameter(0).GetPlainString();
const gd::String &behaviorName = action.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& objectName = action.GetParameter(0).GetPlainString();
const gd::String& behaviorName = action.GetParameter(1).GetPlainString();
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
const BehaviorMetadata &autoInfo =
const BehaviorMetadata& autoInfo =
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
AddIncludeFiles(autoInfo.includeFiles);
@@ -634,15 +692,15 @@ gd::String EventsCodeGenerator::GenerateActionCode(
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode +=
GenerateBehaviorAction(realObjects[i],
behaviorName,
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
actionCode += GenerateBehaviorAction(realObjects[i],
behaviorName,
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName,
optionalAsyncCallbackId);
context.SetNoCurrentObject();
}
@@ -654,7 +712,8 @@ gd::String EventsCodeGenerator::GenerateActionCode(
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
optionalAsyncCallbackName,
optionalAsyncCallbackId);
}
return actionCode;
@@ -667,8 +726,8 @@ gd::String EventsCodeGenerator::GenerateLocalVariablesStackAccessor() {
}
gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
const gd::Expression &variableExpression,
EventsCodeGenerationContext &context) {
const gd::Expression& variableExpression,
EventsCodeGenerationContext& context) {
const auto variableName = gd::ExpressionVariableNameFinder::GetVariableName(
*variableExpression.GetRootNode());
@@ -679,8 +738,12 @@ gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
: "scenevar";
return gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, variableParameterType,
variableExpression.GetPlainString(), "", "AllowUndeclaredVariable");
*this,
context,
variableParameterType,
variableExpression.GetPlainString(),
"",
"AllowUndeclaredVariable");
}
const EventsCodeGenerator::CallbackDescriptor
@@ -727,6 +790,11 @@ EventsCodeGenerator::GenerateCallback(
AddCustomCodeOutsideMain(callbackCode);
const gd::String idToCallbackMapUpdate = GetCodeNamespaceAccessor() +
"idToCallbackMap.set(" + callbackID +
", " + callbackFunctionName + ");\n";
AddCustomCodeOutsideMain(idToCallbackMapUpdate);
std::set<gd::String> requiredObjects;
// Build the list of all objects required by the callback. Any object that has
// already been declared could have gone through previous object picking, so
@@ -769,7 +837,7 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
} else {
outputCode += actionCode;
}
outputCode += "}";
outputCode += "}\n";
}
return outputCode;
@@ -786,13 +854,28 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
if (ParameterMetadata::IsExpression("number", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "number", parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
"number",
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsExpression("string", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "string", parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
"string",
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsExpression("variable", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, metadata.GetType(), parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
metadata.GetType(),
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsObject(metadata.GetType())) {
// It would be possible to run a gd::ExpressionCodeGenerator if later
// objects can have nested objects, or function returning objects.
@@ -827,7 +910,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
metadata.GetType() == "atlasResource" ||
metadata.GetType() == "spineResource" ||
// Deprecated, old parameter names:
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
metadata.GetType() == "password" ||
metadata.GetType() == "musicfile" ||
metadata.GetType() == "soundfile") {
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.GetType() == "mouse") {
@@ -977,7 +1061,8 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
for (std::size_t eId = 0; eId < events.size(); ++eId) {
auto& event = events[eId];
if (event.HasVariables()) {
GetProjectScopedContainers().GetVariablesContainersList().Push(event.GetVariables());
GetProjectScopedContainers().GetVariablesContainersList().Push(
event.GetVariables());
}
// Each event has its own context : Objects picked in an event are totally
@@ -1102,7 +1187,7 @@ gd::String EventsCodeGenerator::GenerateFreeCondition(
instrInfos.codeExtraInformation.functionCallName);
} else {
predicate = instrInfos.codeExtraInformation.functionCallName + "(" +
GenerateArgumentsList(arguments, 0) + ")";
GenerateArgumentsList(arguments, 0) + ")";
}
// Add logical not if needed
@@ -1146,7 +1231,7 @@ gd::String EventsCodeGenerator::GenerateObjectCondition(
instrInfos, arguments, objectFunctionCallNamePart, 1);
} else {
predicate = objectFunctionCallNamePart + "(" +
GenerateArgumentsList(arguments, 1) + ")";
GenerateArgumentsList(arguments, 1) + ")";
}
if (conditionInverted) predicate = GenerateNegatedPredicate(predicate);
@@ -1178,18 +1263,20 @@ gd::String EventsCodeGenerator::GenerateBehaviorCondition(
}
gd::String EventsCodeGenerator::GenerateFreeAction(
const gd::String& functionCallName,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Generate call
gd::String call;
if (instrInfos.codeExtraInformation.type == "number" ||
instrInfos.codeExtraInformation.type == "string" ||
// Boolean actions declared with addExpressionAndConditionAndAction uses
// MutatorAndOrAccessor even though they don't declare an operator parameter.
// Boolean operators are only used with SetMutators or SetCustomCodeGenerator.
// MutatorAndOrAccessor even though they don't declare an operator
// parameter. Boolean operators are only used with SetMutators or
// SetCustomCodeGenerator.
(instrInfos.codeExtraInformation.type == "boolean" &&
instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::AccessType::Mutators)) {
@@ -1202,23 +1289,19 @@ gd::String EventsCodeGenerator::GenerateFreeAction(
instrInfos.codeExtraInformation.optionalAssociatedInstruction);
else if (instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::Mutators)
call =
GenerateMutatorCall(instrInfos,
arguments,
functionCallName);
call = GenerateMutatorCall(instrInfos, arguments, functionCallName);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
functionCallName);
call =
GenerateCompoundOperatorCall(instrInfos, arguments, functionCallName);
} else {
call = functionCallName + "(" +
GenerateArgumentsList(arguments) + ")";
call = functionCallName + "(" + GenerateArgumentsList(arguments) + ")";
}
if (!optionalAsyncCallbackName.empty())
if (!optionalAsyncCallbackName.empty() && !optionalAsyncCallbackId.empty()) {
call = "runtimeScene.getAsyncTasksManager().addTask(" + call + ", " +
optionalAsyncCallbackName + ")";
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
", asyncObjectsList)";
}
return call + ";\n";
}
@@ -1230,7 +1313,8 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Create call
gd::String call;
if ((instrInfos.codeExtraInformation.type == "number" ||
@@ -1271,7 +1355,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Create call
gd::String call;
if ((instrInfos.codeExtraInformation.type == "number" ||
@@ -1286,17 +1371,13 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
2);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
functionCallName,
2);
instrInfos, arguments, functionCallName, 2);
return "For each picked object \"" + objectName + "\", call " + call +
" for behavior \"" + behaviorName + "\".\n";
} else {
gd::String argumentsStr = GenerateArgumentsList(arguments, 2);
call = functionCallName + "(" +
argumentsStr + ")";
call = functionCallName + "(" + argumentsStr + ")";
return "For each picked object \"" + objectName + "\", call " + call + "(" +
argumentsStr + ")" + " for behavior \"" + behaviorName + "\"" +
@@ -1351,42 +1432,47 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
return argumentsStr;
}
gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
gd::String EventsCodeGenerator::GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property) {
return "getProperty" + property.GetName() + "()";
}
gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode) {
return "setProperty" + property.GetName() + "(" + operandCode + ")";
}
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
gd::String EventsCodeGenerator::GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata &parameter) {
return "getParameter" + parameter.GetName() + "()";
}
const gd::ParameterMetadata& parameter) {
return "getParameter" + parameter.GetName() + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_)
: platform(platform_),
projectScopedContainers(gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project_, layout)),
projectScopedContainers(
gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project_,
layout)),
hasProjectAndLayout(true),
project(&project_),
scene(&layout),
@@ -1395,7 +1481,7 @@ EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
maxCustomConditionsDepth(0),
maxConditionsListsSize(0),
eventsListNextUniqueId(0),
diagnosticReport(nullptr){};
diagnosticReport(nullptr) {};
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
@@ -1410,6 +1496,6 @@ EventsCodeGenerator::EventsCodeGenerator(
maxCustomConditionsDepth(0),
maxConditionsListsSize(0),
eventsListNextUniqueId(0),
diagnosticReport(nullptr){};
diagnosticReport(nullptr) {};
} // namespace gd

View File

@@ -9,9 +9,9 @@
#include <utility>
#include <vector>
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
@@ -62,7 +62,7 @@ class GD_CORE_API EventsCodeGenerator {
EventsCodeGenerator(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
virtual ~EventsCodeGenerator() {};
/**
* \brief Preprocess an events list (replacing for example links with the
@@ -160,7 +160,8 @@ class GD_CORE_API EventsCodeGenerator {
gd::String GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
struct CallbackDescriptor {
CallbackDescriptor(const gd::String functionName_,
@@ -168,7 +169,7 @@ class GD_CORE_API EventsCodeGenerator {
const std::set<gd::String> requiredObjects_)
: functionName(functionName_),
argumentsList(argumentsList_),
requiredObjects(requiredObjects_){};
requiredObjects(requiredObjects_) {};
/**
* The name by which the function can be invoked.
*/
@@ -338,9 +339,9 @@ class GD_CORE_API EventsCodeGenerator {
}
/**
* @brief Give access to the project scoped containers as code generation might
* push and pop variable containers (for local variables).
* This could be passed as a parameter recursively in code generation, but this requires
* @brief Give access to the project scoped containers as code generation
* might push and pop variable containers (for local variables). This could be
* passed as a parameter recursively in code generation, but this requires
* heavy refactoring. Instead, we use this single instance.
*/
gd::ProjectScopedContainers& GetProjectScopedContainers() {
@@ -387,9 +388,7 @@ class GD_CORE_API EventsCodeGenerator {
diagnosticReport = diagnosticReport_;
}
gd::DiagnosticReport* GetDiagnosticReport() {
return diagnosticReport;
}
gd::DiagnosticReport* GetDiagnosticReport() { return diagnosticReport; }
/**
* \brief Generate the full name for accessing to a boolean variable used for
@@ -513,16 +512,16 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Generate an any variable getter that fallbacks on scene variable for
* compatibility reason.
*/
gd::String
GenerateAnyOrSceneVariableGetter(const gd::Expression &variableExpression,
EventsCodeGenerationContext &context);
gd::String GenerateAnyOrSceneVariableGetter(
const gd::Expression& variableExpression,
EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode);
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode);
protected:
protected:
virtual const gd::String GenerateRelationalOperatorCodes(
const gd::String& operatorString);
@@ -643,16 +642,16 @@ protected:
gd::EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property);
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property);
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String
GenerateParameterGetterWithoutCasting(const gd::ParameterMetadata &parameter);
virtual gd::String GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata& parameter);
/**
* \brief Generate the code to reference an object which is
@@ -769,7 +768,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
virtual gd::String GenerateObjectAction(
const gd::String& objectName,
@@ -778,7 +778,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
virtual gd::String GenerateBehaviorAction(
const gd::String& objectName,
@@ -788,7 +789,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
gd::String GenerateRelationalOperatorCall(
const gd::InstructionMetadata& instrInfos,
@@ -837,9 +839,8 @@ protected:
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName);
void CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos);
bool CheckBehaviorParameters(const gd::Instruction& instruction,
const gd::InstructionMetadata& instrInfos);
const gd::Platform& platform; ///< The platform being used.
@@ -876,4 +877,3 @@ protected:
};
} // namespace gd

View File

@@ -286,6 +286,20 @@ class GD_CORE_API BaseEvent {
* \brief True if the event should be folded in the events editor.
*/
bool IsFolded() const { return folded; }
/**
* \brief Set the AI generated event ID.
*/
void SetAiGeneratedEventId(const gd::String& aiGeneratedEventId_) {
aiGeneratedEventId = aiGeneratedEventId_;
}
/**
* \brief Get the AI generated event ID.
*/
const gd::String& GetAiGeneratedEventId() const {
return aiGeneratedEventId;
}
///@}
std::weak_ptr<gd::BaseEvent>
@@ -304,6 +318,7 @@ class GD_CORE_API BaseEvent {
bool disabled; ///< True if the event is disabled and must not be executed
gd::String type; ///< Type of the event. Must be assigned at the creation.
///< Used for saving the event for instance.
gd::String aiGeneratedEventId; ///< When generated by an AI/external tool.
static gd::EventsList badSubEvents;
static gd::VariablesContainer badLocalVariables;

View File

@@ -221,6 +221,8 @@ void EventsListSerialization::UnserializeEventsFrom(
event->SetDisabled(eventElem.GetBoolAttribute("disabled", false));
event->SetFolded(eventElem.GetBoolAttribute("folded", false));
event->SetAiGeneratedEventId(
eventElem.GetStringAttribute("aiGeneratedEventId", ""));
list.InsertEvent(event, list.GetEventsCount());
}
@@ -236,6 +238,8 @@ void EventsListSerialization::SerializeEventsTo(const EventsList& list,
if (event.IsDisabled())
eventElem.SetAttribute("disabled", event.IsDisabled());
if (event.IsFolded()) eventElem.SetAttribute("folded", event.IsFolded());
if (!event.GetAiGeneratedEventId().empty())
eventElem.SetAttribute("aiGeneratedEventId", event.GetAiGeneratedEventId());
eventElem.AddChild("type").SetValue(event.GetType());
event.SerializeTo(eventElem);

View File

@@ -37,8 +37,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.SetIcon("res/actions/position24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Angle"))
.SetIcon("res/actions/direction24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
gd::ObjectMetadata& obj = extension.AddObject<gd::ObjectConfiguration>(
"", _("Base object"), _("Base object"), "res/objeticon24.png");
@@ -235,7 +235,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddAction("SetAngle",
_("Angle"),
_("Change the angle of rotation of an object (in degrees)."),
_("Change the angle of rotation of an object (in degrees). For "
"3D objects, this is the rotation around the Z axis."),
_("the angle"),
_("Angle"),
"res/actions/direction24_black.png",
@@ -250,7 +251,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddAction("Rotate",
_("Rotate"),
_("Rotate an object, clockwise if the speed is positive, "
"counterclockwise otherwise."),
"counterclockwise otherwise. For 3D objects, this is the "
"rotation around the Z axis."),
_("Rotate _PARAM0_ at speed _PARAM1_ deg/second"),
_("Angle"),
"res/actions/rotate24_black.png",
@@ -634,7 +636,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddCondition("Angle",
_("Angle"),
_("Compare the angle of the specified object."),
_("Compare the angle, in degrees, of the specified object. "
"For 3D objects, this is the angle around the Z axis."),
_("the angle (in degrees)"),
_("Angle"),
"res/conditions/direction24_black.png",
@@ -835,14 +838,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced()
.SetRelevantForLayoutEventsOnly();
obj.AddAction(
"PushBooleanToObjectVariable",
_("Add value to object array variable"),
_("Adds a boolean to the end of an object array variable."),
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
_("Variables Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
obj.AddAction("PushBooleanToObjectVariable",
_("Add value to object array variable"),
_("Adds a boolean to the end of an object array variable."),
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
_("Variables Arrays and structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("object", _("Object"))
.AddParameter("objectvar", _("Array variable"))
.AddParameter("trueorfalse", _("Boolean to add"))
@@ -1268,7 +1270,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpression("Angle",
_("Angle"),
_("Current angle, in degrees, of the object"),
_("Current angle, in degrees, of the object. For 3D "
"objects, this is the angle around the Z axis."),
_("Angle"),
"res/actions/direction_black.png")
.AddParameter("object", _("Object"));
@@ -1571,7 +1574,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
extension
.AddAction("Create",
_("Create an object"),
_("Create an object at specified position"),
_("Create an instance of the object at the specified position."
"The created object instance will be available for the next "
"actions and sub-events."),
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ "
"(layer: _PARAM4_)"),
"",

View File

@@ -18,21 +18,21 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("AnimatableCapability",
_("Animatable capability"),
_("Animate objects."),
_("Objects with animations"),
_("Actions and conditions for objects having animations (sprite, 3D models...)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Animatable capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects with animations"))
.SetIcon("res/actions/animation24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Animations and images"))
.SetIcon("res/actions/animation24.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"AnimatableBehavior",
_("Animatable capability"),
_("Objects with animations"),
"Animation",
_("Animate objects."),
_("Actions and conditions for objects having animations (sprite, 3D models...).."),
"",
"res/actions/animation24.png",
"AnimatableBehavior",

View File

@@ -18,8 +18,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("EffectCapability",
_("Effect capability"),
_("Apply visual effects to objects."),
_("Objects with effects"),
_("Actions/conditions to enable/disable and change parameters of visual effects applied on objects."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
@@ -28,9 +28,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
gd::BehaviorMetadata& aut = extension.AddBehavior(
"EffectBehavior",
_("Effect capability"),
_("Objects with effects"),
"Effect",
_("Apply visual effects to objects."),
_("Actions/conditions to enable/disable and change parameters of visual effects applied on objects."),
"",
"res/actions/effect_black.svg",
"EffectBehavior",

View File

@@ -18,8 +18,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("FlippableCapability",
_("Flippable capability"),
_("Flip objects."),
_("Flippable objects"),
_("Actions/conditions for objects which can be flipped horizontally or vertically."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
@@ -28,9 +28,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
gd::BehaviorMetadata& aut = extension.AddBehavior(
"FlippableBehavior",
_("Flippable capability"),
_("Flippable objects"),
"Flippable",
_("Flip objects."),
_("Actions/conditions for objects which can be flipped horizontally or vertically."),
"",
"res/actions/flipX24.png",
"FlippableBehavior",

View File

@@ -18,27 +18,30 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("OpacityCapability",
_("Opacity capability"),
_("Change the object opacity."),
_("Objects with opacity"),
_("Action/condition/expression to change or "
"check the opacity of an object (0-255)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Opacity capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects with opacity"))
.SetIcon("res/actions/opacity24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Visibility"))
.SetIcon("res/actions/opacity24.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"OpacityBehavior",
_("Opacity capability"),
"Opacity",
_("Change the object opacity."),
"",
"res/actions/opacity24.png",
"OpacityBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
gd::BehaviorMetadata& aut =
extension
.AddBehavior("OpacityBehavior",
_("Objects with opacity"),
"Opacity",
_("Action/condition/expression to change or check the "
"opacity of an object (0-255)."),
"",
"res/actions/opacity24.png",
"OpacityBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"number",
@@ -52,8 +55,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "OpacityBehavior")
.UseStandardParameters(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity (0-255)")))
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity (0-255)")))
.SetFunctionName("setOpacity")
.SetGetter("getOpacity");
aut.GetAllExpressions()["Value"].SetGroup("");

View File

@@ -16,11 +16,13 @@ namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::PlatformExtension &extension) {
extension
.SetExtensionInformation("ResizableCapability",
_("Resizable capability"),
_("Change the object dimensions."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionInformation(
"ResizableCapability",
_("Resizable objects"),
_("Change or compare the size (width/height) of an object which can "
"be resized (i.e: most objects)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
@@ -28,9 +30,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::BehaviorMetadata &aut =
extension
.AddBehavior("ResizableBehavior",
_("Resizable capability"),
_("Resizable objects"),
"Resizable",
_("Change the object dimensions."),
_("Change or compare the size (width/height) of an "
"object which can be resized (i.e: most objects)."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",

View File

@@ -18,27 +18,30 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("ScalableCapability",
_("Scalable capability"),
_("Change the object scale."),
_("Scalable objects"),
_("Actions/conditions/expression to change or "
"check the scale of an object (default: 1)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable capability"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable objects"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"ScalableBehavior",
_("Scalable capability"),
"Scale",
_("Change the object scale."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
gd::BehaviorMetadata& aut =
extension
.AddBehavior("ScalableBehavior",
_("Scalable objects"),
"Scale",
_("Actions/conditions/expression to change or check the "
"scale of an object (default: 1)."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"number",

View File

@@ -18,19 +18,19 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTextContainerExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("TextContainerCapability",
_("Text capability"),
_("Animate objects."),
_("Objects containing a text"),
_("Allows an object to contain a text, usually shown on screen, that can be modified."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Text capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects containing a text"))
.SetIcon("res/conditions/text24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TextContainerBehavior",
_("Text capability"),
_("Objects containing a text"),
"Text",
_("Access objects text."),
_("Allows an object to contain a text, usually shown on screen, that can be modified."),
"",
"res/conditions/text24_black.png",
"TextContainerBehavior",

View File

@@ -16,7 +16,9 @@ BuiltinExtensionsImplementer::ImplementsCommonConversionsExtension(
.SetExtensionInformation(
"BuiltinCommonConversions",
_("Conversion"),
"Expressions to convert number, texts and quantities.",
"Expressions to convert numbers to string, strings to numbers, "
"angles (degrees from/to radians) and a GDevelop variable to/from a "
"JSON string.",
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/common-conversions");
@@ -41,7 +43,7 @@ BuiltinExtensionsImplementer::ImplementsCommonConversionsExtension(
extension
.AddStrExpression("LargeNumberToString",
_("Number > Text ( without scientific notation )"),
_("Number > Text (without scientific notation)"),
_("Convert the result of the expression to text, "
"without using the scientific notation"),
"",
@@ -72,7 +74,8 @@ BuiltinExtensionsImplementer::ImplementsCommonConversionsExtension(
_("Convert a variable to JSON"),
_("JSON"),
"res/conditions/toujours24_black.png")
.AddParameter("variable", _("The variable to be stringified"),
.AddParameter("variable",
_("The variable to be stringified"),
"AllowUndeclaredVariable");
// Deprecated

View File

@@ -59,36 +59,44 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
// end of compatibility code
extension
.AddCondition("Or",
_("Or"),
_("Check if one of the sub conditions is true"),
_("If one of these conditions is true:"),
"",
"res/conditions/or24_black.png",
"res/conditions/or_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();
extension
.AddCondition("And",
_("And"),
_("Check if all sub conditions are true"),
_("If all of these conditions are true:"),
"",
"res/conditions/and24_black.png",
"res/conditions/and_black.png")
.AddCondition(
"Or",
_("Or"),
_("Checks if at least one sub-condition is true. If no "
"sub-condition is specified, it will always be false. "
"This is rarely used — multiple events and sub-events are "
"usually a better approach."),
_("If one of these conditions is true:"),
"",
"res/conditions/or24_black.png",
"res/conditions/or_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();
extension
.AddCondition(
"Not",
_("Not"),
_("Return the contrary of the result of the sub conditions"),
_("Invert the logical result of these conditions:"),
"And",
_("And"),
_("Checks if all sub-conditions are true. If no sub-condition is "
"specified, it will always be false. This is rarely needed, as "
"events already check all conditions before running actions."),
_("If all of these conditions are true:"),
"",
"res/conditions/not24_black.png",
"res/conditions/not_black.png")
"res/conditions/and24_black.png",
"res/conditions/and_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();
extension
.AddCondition("Not",
_("Not"),
_("Returns the opposite of the sub-condition(s) result. "
"This is rarely needed, as most conditions can be "
"inverted or expressed more simply."),
_("Invert the logical result of these conditions:"),
"",
"res/conditions/not24_black.png",
"res/conditions/not_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();

View File

@@ -42,13 +42,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFileExtension(
extension
.AddAction(
"LoadFile",
_("Load a storage in memory"),
_("This action loads the specified storage in memory, so you can "
"write and read it.\nYou can open and write without using this "
"action, but it will be slower.\nIf you use this action, do not "
"forget to unload the storage from memory."),
_("Load storage _PARAM0_ in memory"),
"",
_("Manually preload a storage in memory"),
_("Forces the specified storage to be loaded and kept in "
"memory, allowing faster reads/writes. "
"However, it requires manual management: if you use this "
"action, you *must* also unload the storage manually when "
"it's no longer needed to ensure data is persisted.\n\n"
"Unless you have a specific performance need, avoid using this "
"action. The system already handles loading/unloading "
"automatically."),
_("Load data storage _PARAM0_ in memory"),
_("Advanced"),
"res/actions/fichier24.png",
"res/actions/fichier.png")
.AddParameter("string", _("Storage name"))
@@ -56,11 +60,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFileExtension(
extension
.AddAction("UnloadFile",
_("Close a storage"),
_("This action closes the structured data previously loaded "
_("Manually unload and persist a storage"),
_("Close the specified storage previously loaded "
"in memory, saving all changes made."),
_("Close structured data _PARAM0_"),
"",
_("Unload and persist data storage _PARAM0_"),
_("Advanced"),
"res/actions/fichier24.png",
"res/actions/fichier.png")
.AddParameter("string", _("Storage name"))

View File

@@ -15,10 +15,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
.SetExtensionInformation(
"BuiltinKeyboard",
_("Keyboard"),
_("Allows your game to respond to keyboard input. Note that this "
_("Conditions to check keys pressed on a keyboard. Note that this "
"does not work with on-screen keyboard on touch devices: use "
"instead conditions related to touch when making a game for "
"mobile/touchscreen devices."),
"instead mouse/touch conditions when making a game for "
"mobile/touchscreen devices or when making a new game from "
"scratch."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/keyboard")
@@ -51,10 +52,25 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
.SetHidden();
extension
.AddCondition("KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed"),
_("_PARAM1_ key is pressed"),
.AddCondition(
"KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed. This stays true as long as "
"the key is held down. To check if a key was pressed during "
"the frame, use \"Key just pressed\" instead."),
_("_PARAM1_ key is pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("keyboardKey", _("Key to check"))
.MarkAsSimple();
extension
.AddCondition("KeyFromTextJustPressed",
_("Key just pressed"),
_("Check if a key was just pressed."),
_("_PARAM1_ key was just pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
@@ -65,7 +81,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyFromTextReleased",
_("Key released"),
_("Check if a key was just released"),
_("Check if a key was just released."),
_("_PARAM1_ key is released"),
"",
"res/conditions/keyboard24.png",
@@ -84,7 +100,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "");
extension
extension
.AddCondition("AnyKeyReleased",
_("Any key released"),
_("Check if any key is released"),

View File

@@ -72,7 +72,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("normalize",
_("Normalize a value between `min` and `max` to a value between 0 and 1."),
_("Normalize a value between `min` and `max` to a value "
"between 0 and 1."),
_("Remap a value between 0 and 1."),
"",
"res/mathfunction.png")
@@ -124,7 +125,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("mod",
_("Modulo"),
_("x mod y"),
_("Compute \"x mod y\". GDevelop does NOT support the \% "
"operator. Use this mod(x, y) function instead."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("x (as in x mod y)"))
@@ -184,11 +186,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"));
extension
.AddExpression("asinh",
_("Arcsine"),
_("Arcsine"),
"",
"res/mathfunction.png")
.AddExpression(
"asinh", _("Arcsine"), _("Arcsine"), "", "res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -218,11 +217,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"));
extension
.AddExpression("cbrt",
_("Cube root"),
_("Cube root"),
"",
"res/mathfunction.png")
.AddExpression(
"cbrt", _("Cube root"), _("Cube root"), "", "res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -260,12 +256,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"), "", true);
extension
.AddExpression("cos",
_("Cosine"),
_("Cosine of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddExpression(
"cos",
_("Cosine"),
_("Cosine of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -293,29 +290,20 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"));
extension
.AddExpression("int",
_("Round"),
_("Round a number"),
"",
"res/mathfunction.png")
.AddExpression(
"int", _("Round"), _("Round a number"), "", "res/mathfunction.png")
.SetHidden()
.AddParameter("expression", _("Expression"));
extension
.AddExpression("rint",
_("Round"),
_("Round a number"),
"",
"res/mathfunction.png")
.AddExpression(
"rint", _("Round"), _("Round a number"), "", "res/mathfunction.png")
.SetHidden()
.AddParameter("expression", _("Expression"));
extension
.AddExpression("round",
_("Round"),
_("Round a number"),
"",
"res/mathfunction.png")
.AddExpression(
"round", _("Round"), _("Round a number"), "", "res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -324,8 +312,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
_("Round a number to the Nth decimal place"),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"))
.AddParameter("expression", _("Expression"), "", true);
.AddParameter("expression", _("Number to Round"))
.AddParameter("expression", _("Decimal Places"), "", true);
extension
.AddExpression("exp",
@@ -336,19 +324,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"));
extension
.AddExpression("log",
_("Logarithm"),
_("Logarithm"),
"",
"res/mathfunction.png")
.AddExpression(
"log", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
.AddExpression("ln",
_("Logarithm"),
_("Logarithm"),
"",
"res/mathfunction.png")
.AddExpression(
"ln", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
.SetHidden()
.AddParameter("expression", _("Expression"));
@@ -387,11 +369,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("The exponent (n in x^n)"));
extension
.AddExpression("sec",
_("Secant"),
_("Secant"),
"",
"res/mathfunction.png")
.AddExpression(
"sec", _("Secant"), _("Secant"), "", "res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -403,12 +382,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"));
extension
.AddExpression("sin",
_("Sine"),
_("Sine of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddExpression(
"sin",
_("Sine"),
_("Sine of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -428,12 +408,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("Expression"));
extension
.AddExpression("tan",
_("Tangent"),
_("Tangent of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddExpression(
"tan",
_("Tangent"),
_("Tangent of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
extension
@@ -463,26 +444,28 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
.AddParameter("expression", _("x (in a+(b-a)*x)"));
extension
.AddExpression("XFromAngleAndDistance",
_("X position from angle and distance"),
_("Compute the X position when given an angle and distance "
"relative to the origin (0;0). This is also known as "
"getting the cartesian coordinates of a 2D vector, using "
"its polar coordinates."),
"",
"res/mathfunction.png")
.AddExpression(
"XFromAngleAndDistance",
_("X position from angle and distance"),
_("Compute the X position when given an angle and distance "
"relative to the origin (0;0). This is also known as "
"getting the cartesian coordinates of a 2D vector, using "
"its polar coordinates."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Distance"));
extension
.AddExpression("YFromAngleAndDistance",
_("Y position from angle and distance"),
_("Compute the Y position when given an angle and distance "
"relative to the origin (0;0). This is also known as "
"getting the cartesian coordinates of a 2D vector, using "
"its polar coordinates."),
"",
"res/mathfunction.png")
.AddExpression(
"YFromAngleAndDistance",
_("Y position from angle and distance"),
_("Compute the Y position when given an angle and distance "
"relative to the origin (0;0). This is also known as "
"getting the cartesian coordinates of a 2D vector, using "
"its polar coordinates."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Distance"));
@@ -497,7 +480,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("lerpAngle",
_("Lerp (Linear interpolation) between two angles"),
_("Linearly interpolates between two angles (in degrees) by taking the shortest direction around the circle."),
_("Linearly interpolates between two angles (in degrees) "
"by taking the shortest direction around the circle."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Starting angle, in degrees"))

View File

@@ -16,8 +16,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.SetExtensionInformation(
"BuiltinMouse",
_("Mouse and touch"),
"Conditions and actions to handle either the mouse or touches on "
"touchscreen. By default, conditions related to the mouse will also "
"Conditions, actions and expressions to handle either the mouse or "
"touches on a touchscreen. Notably: cursor position, mouse wheel, "
"mouse buttons, touch positions, started/end touches, etc...\n"
"\n"
"By default, conditions related to the mouse will also "
"handle the touches - so that it's easier to handle both in your "
"game. You can disable this behavior if you want to handle them "
"separately in different events.",
@@ -273,28 +276,26 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.SetHidden();
extension
.AddCondition(
"MouseButtonFromTextPressed",
_("Mouse button pressed or touch held"),
_("Check if the specified mouse button is pressed or "
"if a touch is in contact with the screen."),
_("Touch or _PARAM1_ mouse button is down"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCondition("MouseButtonFromTextPressed",
_("Mouse button pressed or touch held"),
_("Check if the specified mouse button is pressed or "
"if a touch is in contact with the screen."),
_("Touch or _PARAM1_ mouse button is down"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("mouseButton", _("Button to check"))
.MarkAsSimple();
extension
.AddCondition(
"MouseButtonFromTextReleased",
_("Mouse button released"),
_("Check if the specified mouse button was released."),
_("Touch or _PARAM1_ mouse button is released"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCondition("MouseButtonFromTextReleased",
_("Mouse button released"),
_("Check if the specified mouse button was released."),
_("Touch or _PARAM1_ mouse button is released"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("mouseButton", _("Button to check"))
.MarkAsSimple();

View File

@@ -15,8 +15,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
.SetExtensionInformation(
"BuiltinNetwork",
_("Network"),
_("Features to send web requests, communicate with external \"APIs\" "
"and other network related tasks."),
_("Actions to send web requests, communicate with external \"APIs\" "
"and other network related tasks. Also contains an action to open "
"a URL on the device browser."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/network")

View File

@@ -4,8 +4,8 @@
* reserved. This project is released under the MIT License.
*/
#include "AllBuiltinExtensions.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Tools/Localization.h"
using namespace std;
namespace gd {
@@ -16,7 +16,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
.SetExtensionInformation(
"BuiltinScene",
_("Scene"),
_("Actions and conditions to manipulate the scenes during the game."),
_("Actions/conditions to change the current scene (or pause it and "
"launch another one, or go back to the previous one), check if a "
"scene or the game has just started/resumed, preload assets of a "
"scene, get the current scene name or loading progress, quit the "
"game, set background color, or disable input when focus is lost."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("" /*TODO: Add a documentation page for this */);
@@ -166,25 +170,28 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
.AddCodeOnlyParameter("currentScene", "");
extension
.AddAction("PrioritizeLoadingOfScene",
_("Preload scene"),
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.AddAction(
"PrioritizeLoadingOfScene",
_("Preload scene"),
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Name of the new scene"))
.MarkAsAdvanced();
extension.AddExpressionAndCondition("number",
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in background for a scene (between 0 and 1)."),
_("_PARAM0_ loading progress"),
_(""),
"res/actions/hourglass_black.svg")
extension
.AddExpressionAndCondition("number",
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in "
"background for a scene (between 0 and 1)."),
_("_PARAM1_ loading progress"),
_(""),
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
@@ -192,13 +199,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
.MarkAsAdvanced();
extension
.AddCondition("AreSceneAssetsLoaded",
_("Scene preloaded"),
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.AddCondition(
"AreSceneAssetsLoaded",
_("Scene preloaded"),
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))

View File

@@ -15,12 +15,13 @@ namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("Sprite",
_("Sprite"),
_("Sprite are animated object which can be used "
"for most elements of a game."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionInformation(
"Sprite",
_("Sprite"),
_("Sprite are animated objects which can be used "
"for most elements of a 2D game."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/sprite");
extension.AddInstructionOrExpressionGroupMetadata(_("Sprite"))
.SetIcon("CppPlatform/Extensions/spriteicon.png");
@@ -30,7 +31,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddObject<SpriteObject>("Sprite",
_("Sprite"),
_("Animated object which can be used for "
"most elements of a game."),
"most elements of a 2D game."),
"CppPlatform/Extensions/spriteicon.png")
.SetCategoryFullName(_("General"))
.SetOpenFullEditorLabel(_("Edit animations"))
@@ -645,11 +646,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/sprite.png")
.AddParameter("object", _("Object"), "Sprite");
obj.AddExpression("AnimationFrameCount",
_("Number of frames"),
_("Number of frames in the current animation of the object"),
_("Animations and images"),
"res/actions/sprite.png")
obj.AddExpression(
"AnimationFrameCount",
_("Number of frames"),
_("Number of frames in the current animation of the object"),
_("Animations and images"),
"res/actions/sprite.png")
.AddParameter("object", _("Object"), "Sprite");
// Deprecated

View File

@@ -16,7 +16,8 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
.SetExtensionInformation(
"BuiltinStringInstructions",
_("Text manipulation"),
"Provides expressions to manipulate strings (also called texts).",
"Provides expressions to manipulate strings (also called texts): new "
"line, upper/lowercase, substring, find, replace, etc...",
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("" /*TODO: Add a documentation page for this */);
@@ -191,7 +192,8 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
"res/conditions/toujours24_black.png")
.AddParameter("string", _("Text in which the replacement must be done"))
.AddParameter("string", _("Text to find inside the first text"))
.AddParameter("string", _("Replacement to put instead of the text to find"));
.AddParameter("string",
_("Replacement to put instead of the text to find"));
extension
.AddStrExpression("StrReplaceAll",
@@ -199,10 +201,11 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
_("Replace all occurrences of a text by another."),
"",
"res/conditions/toujours24_black.png")
.AddParameter("string", _("Text in which the replacement(s) must be done"))
.AddParameter("string",
_("Text in which the replacement(s) must be done"))
.AddParameter("string", _("Text to find inside the first text"))
.AddParameter("string", _("Replacement to put instead of the text to find"));
.AddParameter("string",
_("Replacement to put instead of the text to find"));
}
} // namespace gd

View File

@@ -15,9 +15,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
.SetExtensionInformation(
"BuiltinTime",
_("Timers and time"),
"Actions and conditions to run timers, get the current time or "
"modify the time scale (speed at which the game is running - useful "
"for slow motion effects).",
"Actions and conditions to start, pause or reset scene timers, "
"modify the time scale (speed at which the game "
"is running - useful for slow motion effects). Also contains an "
"action that wait for a delay before running the next actions and "
"sub-events and expressions to read the time scale, time delta of "
"the last frame or timer elapsed time.",
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/timers-and-time");
@@ -43,7 +46,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
.AddCondition("CompareTimer",
_("Value of a scene timer"),
_("Compare the elapsed time of a scene timer. This "
"condition doesn't start the timer."),
"condition doesn't start the timer and will always be "
"false if the timer was not started previously (whatever "
"the comparison being made)."),
_("The timer _PARAM1_ _PARAM2_ _PARAM3_ seconds"),
"",
@@ -190,26 +195,28 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("TimerElapsedTime",
_("Scene timer value"),
_("Value of a scene timer"),
_("Value of a scene timer (in seconds)"),
"",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("identifier", _("Timer's name"), "sceneTimer");
extension
.AddExpression("TimeFromStart",
_("Time elapsed since the beginning of the scene"),
_("Time elapsed since the beginning of the scene"),
"",
"res/actions/time.png")
.AddExpression(
"TimeFromStart",
_("Time elapsed since the beginning of the scene (in seconds)."),
_("Time elapsed since the beginning of the scene (in seconds)."),
"",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "");
extension
.AddExpression("TempsDebut",
_("Time elapsed since the beginning of the scene"),
_("Time elapsed since the beginning of the scene"),
"",
"res/actions/time.png")
.AddExpression(
"TempsDebut",
_("Time elapsed since the beginning of the scene (in seconds)."),
_("Time elapsed since the beginning of the scene (in seconds)."),
"",
"res/actions/time.png")
.SetHidden()
.AddCodeOnlyParameter("currentScene", "");
@@ -224,16 +231,21 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("Time",
_("Current time"),
_("Current time"),
_("Gives the current time"),
"",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter(
"stringWithSelector",
_("Hour: hour - Minutes: min - Seconds: sec - Day of month: "
"mday - Months since January: mon - Year since 1900: year - Days "
"since Sunday: wday - Days since Jan 1st: yday - Timestamp (ms): "
"timestamp\""),
_("- Hour of the day: \"hour\"\n"
"- Minutes: \"min\"\n"
"- Seconds: \"sec\"\n"
"- Day of month: \"mday\"\n"
"- Months since January: \"mon\"\n"
"- Year since 1900: \"year\"\n"
"- Days since Sunday: \"wday\"\n"
"- Days since Jan 1st: \"yday\"\n"
"- Timestamp (ms): \"timestamp\""),
"[\"hour\", \"min\", \"sec\", \"mon\", \"year\", \"wday\", \"mday\", "
"\"yday\", \"timestamp\"]");
}

View File

@@ -15,16 +15,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsWindowExtension(
.SetExtensionInformation(
"BuiltinWindow",
_("Game window and resolution"),
"Provides actions and conditions to manipulate the game window. "
"Actions and conditions to manipulate the game window or change how "
"the game is resized according to the screen size. "
"Depending on the platform on which the game is running, not all of "
"these features can be applied.",
"these features can be applied.\n"
"Also contains expressions to read the screen size.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/all-features/window");
extension
.AddInstructionOrExpressionGroupMetadata(
_("Game window and resolution"))
.AddInstructionOrExpressionGroupMetadata(_("Game window and resolution"))
.SetIcon("res/actions/window24.png");
extension

View File

@@ -298,6 +298,19 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
return *this;
}
/**
* Check if the behavior can be used on objects from event-based objects.
*/
bool IsRelevantForChildObjects() const { return isRelevantForChildObjects; }
/**
* Set that behavior can't be used on objects from event-based objects.
*/
BehaviorMetadata &MarkAsIrrelevantForChildObjects() {
isRelevantForChildObjects = false;
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
@@ -393,6 +406,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
mutable std::vector<gd::String> requiredBehaviors;
bool isPrivate = false;
bool isHidden = false;
bool isRelevantForChildObjects = true;
gd::String openFullEditorLabel;
QuickCustomization::Visibility quickCustomizationVisibility = QuickCustomization::Visibility::Default;

View File

@@ -277,6 +277,10 @@ class GD_CORE_API MetadataProvider {
return &metadata == &badObjectInfo;
}
static bool IsBadEffectMetadata(const gd::EffectMetadata& metadata) {
return &metadata == &badEffectMetadata;
}
virtual ~MetadataProvider();
private:

View File

@@ -194,7 +194,8 @@ void ParameterMetadataTools::IterateOverParameters(
[&fn](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& lastObjectName,
size_t lastObjectIndex) {
fn(parameterMetadata, parameterValue, lastObjectName);
});
}
@@ -205,8 +206,10 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn) {
const gd::String& lastObjectName,
size_t lastObjectIndex)> fn) {
gd::String lastObjectName = "";
size_t lastObjectIndex = 0;
for (std::size_t pNb = 0; pNb < parametersMetadata.GetParametersCount();
++pNb) {
const gd::ParameterMetadata &parameterMetadata =
@@ -218,15 +221,17 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
? Expression(parameterMetadata.GetDefaultValue())
: parameterValue;
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName);
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName, lastObjectIndex);
// Memorize the last object name. By convention, parameters that require
// an object (mainly, "objectvar" and "behavior") should be placed after
// the object in the list of parameters (if possible, just after).
// Search "lastObjectName" in the codebase for other place where this
// convention is enforced.
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType()))
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType())) {
lastObjectName = parameterValueOrDefault.GetPlainString();
lastObjectIndex = pNb;
}
}
}

View File

@@ -64,7 +64,8 @@ class GD_CORE_API ParameterMetadataTools {
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn);
const gd::String& lastObjectName,
size_t lastObjectIndex)> fn);
/**
* Iterate over the parameters of a FunctionCallNode.

View File

@@ -779,6 +779,26 @@ gd::String PlatformExtension::GetBehaviorFullType(
return extensionName + separator + behaviorName;
}
gd::String PlatformExtension::GetExtensionFromFullBehaviorType(
const gd::String& type) {
const auto separatorIndex =
type.find(PlatformExtension::GetNamespaceSeparator());
if (separatorIndex == std::string::npos) {
return "";
}
return type.substr(0, separatorIndex);
}
gd::String PlatformExtension::GetBehaviorNameFromFullBehaviorType(
const gd::String& type) {
const auto separatorIndex =
type.find(PlatformExtension::GetNamespaceSeparator());
if (separatorIndex == std::string::npos) {
return "";
}
return type.substr(separatorIndex + 2);
}
gd::String PlatformExtension::GetObjectEventsFunctionFullType(
const gd::String& extensionName,
const gd::String& objectName,

View File

@@ -651,6 +651,10 @@ class GD_CORE_API PlatformExtension {
static gd::String GetBehaviorFullType(const gd::String& extensionName,
const gd::String& behaviorName);
static gd::String GetExtensionFromFullBehaviorType(const gd::String& type);
static gd::String GetBehaviorNameFromFullBehaviorType(const gd::String& type);
static gd::String GetObjectEventsFunctionFullType(
const gd::String& extensionName,
const gd::String& objectName,

View File

@@ -29,7 +29,7 @@ bool BehaviorParametersFiller::DoVisitInstruction(gd::Instruction &instruction,
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (parameterMetadata.GetValueTypeMetadata().IsBehavior() &&
parameterValue.GetPlainString().length() == 0) {

View File

@@ -108,12 +108,10 @@ bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String& type = parameterMetadata.GetType();
if (gd::ParameterMetadata::IsBehavior(type)) {

View File

@@ -183,12 +183,10 @@ bool EventsParameterReplacer::DoVisitInstruction(gd::Instruction& instruction,
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (!gd::EventsParameterReplacer::CanContainParameter(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -217,12 +217,10 @@ bool EventsPropertyReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (!gd::EventsPropertyReplacer::CanContainProperty(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -334,7 +334,7 @@ private:
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (!gd::EventsObjectReplacer::CanContainObject(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -42,18 +42,16 @@ bool EventsVariableInstructionTypeSwitcher::DoVisitInstruction(gd::Instruction&
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) ||
!gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction(
instruction.GetType())) {
return;
return;
}
const auto variableName =
gd::ExpressionVariableNameFinder::GetVariableName(
@@ -72,10 +70,11 @@ bool EventsVariableInstructionTypeSwitcher::DoVisitInstruction(gd::Instruction&
.GetObjectOrGroupVariablesContainer(lastObjectName);
}
} else if (type == "variableOrProperty") {
variablesContainer =
&GetProjectScopedContainers()
.GetVariablesContainersList()
.GetVariablesContainerFromVariableOrPropertyName(variableName);
variablesContainer =
&GetProjectScopedContainers()
.GetVariablesContainersList()
.GetVariablesContainerFromVariableOrPropertyName(
variableName);
} else {
if (GetProjectScopedContainers().GetVariablesContainersList().Has(
variableName)) {

View File

@@ -448,12 +448,10 @@ bool EventsVariableReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&

View File

@@ -150,7 +150,7 @@ bool ProjectElementRenamer::DoVisitInstruction(gd::Instruction &instruction,
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (parameterMetadata.GetType() == "layer") {
if (parameterValue.GetPlainString().length() < 2) {
// This is either the base layer or an invalid layer name.

View File

@@ -0,0 +1,154 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "EventsBasedObjectVariantHelper.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectGroup.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
namespace gd {
void EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
const gd::Project &project, gd::EventsBasedObject &eventsBasedObject) {
auto &defaultObjects = eventsBasedObject.GetDefaultVariant().GetObjects();
for (const auto &variant :
eventsBasedObject.GetVariants().GetInternalVector()) {
auto &objects = variant->GetObjects();
// Delete extra objects
for (auto it = objects.GetObjects().begin();
it != objects.GetObjects().end(); ++it) {
const auto &objectName = it->get()->GetName();
if (!defaultObjects.HasObjectNamed(objectName)) {
variant->GetInitialInstances().RemoveInitialInstancesOfObject(
objectName);
// Do it in last because it unalloc objectName.
objects.RemoveObject(objectName);
--it;
}
}
for (const auto &defaultObject : defaultObjects.GetObjects()) {
const auto &objectName = defaultObject->GetName();
const auto &defaultVariables = defaultObject->GetVariables();
const auto &defaultBehaviors = defaultObject->GetAllBehaviorContents();
// Copy missing objects
if (!objects.HasObjectNamed(objectName)) {
objects.InsertObject(*defaultObject,
defaultObjects.GetObjectPosition(objectName));
objects.AddMissingObjectsInRootFolder();
continue;
}
// Change object types
auto &object = objects.GetObject(objectName);
if (object.GetType() != defaultObject->GetType()) {
// Keep a copy of the old object.
auto oldObject = objects.GetObject(objectName);
objects.RemoveObject(objectName);
objects.InsertObject(*defaultObject,
defaultObjects.GetObjectPosition(objectName));
object.CopyWithoutConfiguration(oldObject);
objects.AddMissingObjectsInRootFolder();
}
// Copy missing behaviors
for (const auto &pair : defaultBehaviors) {
const auto &behaviorName = pair.first;
const auto &defaultBehavior = pair.second;
if (object.HasBehaviorNamed(behaviorName) &&
object.GetBehavior(behaviorName).GetTypeName() !=
defaultBehavior->GetTypeName()) {
object.RemoveBehavior(behaviorName);
}
if (!object.HasBehaviorNamed(behaviorName)) {
auto *behavior = object.AddNewBehavior(
project, defaultBehavior->GetTypeName(), behaviorName);
gd::SerializerElement element;
defaultBehavior->SerializeTo(element);
behavior->UnserializeFrom(element);
}
}
// Delete extra behaviors
for (auto &behaviorName : object.GetAllBehaviorNames()) {
if (!defaultObject->HasBehaviorNamed(behaviorName)) {
object.RemoveBehavior(behaviorName);
}
}
// Sort and copy missing variables
auto &variables = object.GetVariables();
for (size_t defaultVariableIndex = 0;
defaultVariableIndex < defaultVariables.Count();
defaultVariableIndex++) {
const auto &variableName =
defaultVariables.GetNameAt(defaultVariableIndex);
const auto &defaultVariable =
defaultVariables.Get(defaultVariableIndex);
auto variableIndex = variables.GetPosition(variableName);
if (variableIndex == gd::String::npos) {
variables.Insert(variableName, defaultVariable, defaultVariableIndex);
} else {
variables.Move(variableIndex, defaultVariableIndex);
}
if (variables.Get(variableName).GetType() != defaultVariable.GetType()) {
variables.Remove(variableName);
variables.Insert(variableName, defaultVariable, defaultVariableIndex);
}
}
// Remove extra variables
auto variableToRemoveCount = variables.Count() - defaultVariables.Count();
for (size_t iteration = 0; iteration < variableToRemoveCount;
iteration++) {
variables.Remove(variables.GetNameAt(variables.Count() - 1));
}
// Remove extra instance variables
variant->GetInitialInstances().IterateOverInstances(
[&objectName,
&defaultVariables](gd::InitialInstance &initialInstance) {
if (initialInstance.GetObjectName() != objectName) {
return false;
}
auto &instanceVariables = initialInstance.GetVariables();
for (size_t instanceVariableIndex = 0;
instanceVariableIndex < instanceVariables.Count();
instanceVariableIndex++) {
const auto &variableName =
defaultVariables.GetNameAt(instanceVariableIndex);
if (!defaultVariables.Has(variableName)) {
instanceVariables.Remove(variableName);
}
}
return false;
});
}
auto &defaultObjectGroups =
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjectGroups();
auto &objectGroups = variant->GetObjects().GetObjectGroups();
auto objectGroupsCount = objectGroups.Count();
// Clear groups
for (size_t index = 0; index < objectGroupsCount; index++) {
objectGroups.Remove(objectGroups.Get(0).GetName());
}
// Copy groups
for (size_t index = 0; index < defaultObjectGroups.Count(); index++) {
objectGroups.Insert(defaultObjectGroups.Get(index), index);
}
}
}
} // namespace gd

View File

@@ -0,0 +1,26 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
namespace gd {
class EventsBasedObject;
class Project;
} // namespace gd
namespace gd {
class GD_CORE_API EventsBasedObjectVariantHelper {
public:
/**
* @brief Apply the changes done on events-based object children to all its
* variants.
*/
static void
ComplyVariantsToEventsBasedObject(const gd::Project &project,
gd::EventsBasedObject &eventsBasedObject);
};
} // namespace gd

View File

@@ -17,6 +17,8 @@
#include "GDCore/IDE/Project/ResourcesRenamer.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/CustomBehavior.h"
#include "GDCore/Project/CustomObjectConfiguration.h"
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
@@ -60,9 +62,6 @@ void ObjectAssetSerializer::SerializeTo(
element.SetAttribute("version", "");
element.SetIntAttribute("animationsCount", 1);
element.SetIntAttribute("maxFramesCount", 1);
// TODO Find the right object dimensions.
element.SetIntAttribute("width", 0);
element.SetIntAttribute("height", 0);
SerializerElement &authorsElement = element.AddChild("authors");
authorsElement.ConsiderAsArrayOf("author");
SerializerElement &tagsElement = element.AddChild("tags");
@@ -75,6 +74,28 @@ void ObjectAssetSerializer::SerializeTo(
cleanObject->SerializeTo(objectAssetElement.AddChild("object"));
double width = 0;
double height = 0;
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
if (project.HasEventsBasedObject(object.GetType())) {
SerializerElement &variantsElement =
objectAssetElement.AddChild("variants");
variantsElement.ConsiderAsArrayOf("variant");
const auto *variant = ObjectAssetSerializer::GetVariant(project, object);
if (variant) {
width = variant->GetAreaMaxX() - variant->GetAreaMinX();
height = variant->GetAreaMaxY() - variant->GetAreaMinY();
}
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
project, object, variantsElement, alreadyUsedVariantIdentifiers);
}
// TODO Find the right object dimensions when their is no variant.
element.SetIntAttribute("width", width);
element.SetIntAttribute("height", height);
SerializerElement &resourcesElement =
objectAssetElement.AddChild("resources");
resourcesElement.ConsiderAsArrayOf("resource");
@@ -93,14 +114,24 @@ void ObjectAssetSerializer::SerializeTo(
resourceElement.SetAttribute("name", resource.GetName());
}
std::unordered_set<gd::String> usedExtensionNames;
usedExtensionNames.insert(extensionName);
for (auto &usedVariantIdentifier : alreadyUsedVariantIdentifiers) {
usedExtensionNames.insert(PlatformExtension::GetExtensionFromFullObjectType(
usedVariantIdentifier));
}
SerializerElement &requiredExtensionsElement =
objectAssetElement.AddChild("requiredExtensions");
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", extensionName);
requiredExtensionElement.SetAttribute("extensionVersion", "1.0.0");
for (auto &usedExtensionName : usedExtensionNames) {
if (project.HasEventsFunctionsExtensionNamed(usedExtensionName)) {
auto &extension = project.GetEventsFunctionsExtension(usedExtensionName);
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", usedExtensionName);
requiredExtensionElement.SetAttribute("extensionVersion",
extension.GetVersion());
}
}
// TODO This can be removed when the asset script no longer require it.
@@ -108,4 +139,59 @@ void ObjectAssetSerializer::SerializeTo(
objectAssetElement.AddChild("customization");
customizationElement.ConsiderAsArrayOf("empty");
}
void ObjectAssetSerializer::SerializeUsedVariantsTo(
gd::Project &project, const gd::Object &object,
SerializerElement &variantsElement,
std::unordered_set<gd::String> &alreadyUsedVariantIdentifiers) {
const auto *variant = ObjectAssetSerializer::GetVariant(project, object);
if (!variant) {
return;
}
const auto &variantIdentifier =
object.GetType() + gd::PlatformExtension::GetNamespaceSeparator() +
variant->GetName();
auto insertResult = alreadyUsedVariantIdentifiers.insert(variantIdentifier);
if (!insertResult.second) {
return;
}
SerializerElement &pairElement = variantsElement.AddChild("variant");
pairElement.SetAttribute("objectType", object.GetType());
SerializerElement &variantElement = pairElement.AddChild("variant");
variant->SerializeTo(variantElement);
for (auto &object : variant->GetObjects().GetObjects()) {
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
project, *object, variantsElement, alreadyUsedVariantIdentifiers);
}
}
const gd::EventsBasedObjectVariant *
ObjectAssetSerializer::GetVariant(gd::Project &project,
const gd::Object &object) {
if (!project.HasEventsBasedObject(object.GetType())) {
return nullptr;
}
const auto &eventsBasedObject =
project.GetEventsBasedObject(object.GetType());
const auto &variants = eventsBasedObject.GetVariants();
const auto *customObjectConfiguration =
dynamic_cast<const gd::CustomObjectConfiguration *>(
&object.GetConfiguration());
const auto &variantName = customObjectConfiguration->GetVariantName();
if (!variants.HasVariantNamed(variantName) &&
(customObjectConfiguration
->IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() ||
customObjectConfiguration
->IsForcedToOverrideEventsBasedObjectChildrenConfiguration())) {
return nullptr;
}
const auto &variantIdentifier =
object.GetType() + gd::PlatformExtension::GetNamespaceSeparator() +
variantName;
const auto &variant = variants.HasVariantNamed(variantName)
? variants.GetVariant(variantName)
: eventsBasedObject.GetDefaultVariant();
return &variant;
}
} // namespace gd

View File

@@ -6,6 +6,7 @@
#pragma once
#include <map>
#include <vector>
#include <unordered_set>
#include "GDCore/String.h"
@@ -20,6 +21,7 @@ class InitialInstance;
class SerializerElement;
class EffectsContainer;
class AbstractFileSystem;
class EventsBasedObjectVariant;
} // namespace gd
namespace gd {
@@ -52,6 +54,13 @@ private:
ObjectAssetSerializer(){};
static gd::String GetObjectExtensionName(const gd::Object &object);
static void SerializeUsedVariantsTo(
gd::Project &project, const gd::Object &object,
SerializerElement &variantsElement,
std::unordered_set<gd::String> &alreadyUsedVariantIdentifiers);
static const gd::EventsBasedObjectVariant* GetVariant(gd::Project &project, const gd::Object &object);
};
} // namespace gd

View File

@@ -6,6 +6,7 @@
#include "ObjectVariableHelper.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectGroup.h"
@@ -173,6 +174,7 @@ void ObjectVariableHelper::ApplyChangesToObjects(
groupVariablesContainer.Get(variableName),
variablesContainer.Count());
}
// TODO Check what happens if 2 variables exchange their names.
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
@@ -215,6 +217,7 @@ void ObjectVariableHelper::ApplyChangesToObjectInstances(
destinationVariablesContainer.Remove(variableName);
}
}
// TODO Check what happens if 2 variables exchange their names.
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
@@ -236,6 +239,66 @@ void ObjectVariableHelper::ApplyChangesToObjectInstances(
}
}
}
return false;
});
}
void ObjectVariableHelper::ApplyChangesToVariants(
gd::EventsBasedObject &eventsBasedObject, const gd::String &objectName,
const gd::VariablesChangeset &changeset) {
auto &defaultVariablesContainer = eventsBasedObject.GetDefaultVariant()
.GetObjects()
.GetObject(objectName)
.GetVariables();
for (auto &variant : eventsBasedObject.GetVariants().GetInternalVector()) {
if (!variant->GetObjects().HasObjectNamed(objectName)) {
continue;
}
auto &object = variant->GetObjects().GetObject(objectName);
auto &variablesContainer = object.GetVariables();
for (const gd::String &variableName : changeset.removedVariableNames) {
variablesContainer.Remove(variableName);
}
for (const gd::String &variableName : changeset.addedVariableNames) {
if (variablesContainer.Has(variableName)) {
// It can happens if a child-object already had the variable but it was
// missing in other variant child-object.
continue;
}
variablesContainer.Insert(variableName,
defaultVariablesContainer.Get(variableName),
variablesContainer.Count());
}
// TODO Check what happens if 2 variables exchange their names.
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
if (variablesContainer.Has(newVariableName)) {
// It can happens if a child-object already had the variable but it was
// missing in other variant child-object.
variablesContainer.Remove(oldVariableName);
} else {
variablesContainer.Rename(oldVariableName, newVariableName);
}
}
// Apply type changes
for (const gd::String &variableName : changeset.valueChangedVariableNames) {
size_t index = variablesContainer.GetPosition(variableName);
if (variablesContainer.Has(variableName) &&
variablesContainer.Get(variableName).GetType() !=
defaultVariablesContainer.Get(variableName).GetType()) {
variablesContainer.Remove(variableName);
variablesContainer.Insert(
variableName, defaultVariablesContainer.Get(variableName), index);
}
}
gd::ObjectVariableHelper::ApplyChangesToObjectInstances(
variablesContainer, variant->GetInitialInstances(), objectName,
changeset);
}
}
} // namespace gd

View File

@@ -8,6 +8,7 @@
#include "GDCore/Project/VariablesContainer.h"
namespace gd {
class EventsBasedObject;
class InitialInstancesContainer;
class ObjectsContainersList;
class ObjectsContainer;
@@ -53,7 +54,7 @@ public:
* Objects can be added during the group edition and may not necessarily have
* all the variables initially shared by the group.
*
* \see gd::GroupVariableHelper::MergeVariableContainers
* \see gd::ObjectVariableHelper::MergeVariableContainers
*/
static void FillMissingGroupVariablesToObjects(
gd::ObjectsContainer &globalObjectsContainer,
@@ -72,16 +73,21 @@ public:
const gd::ObjectGroup &objectGroup,
const gd::VariablesChangeset &changeset);
/**
* @brief Apply the changes done on an object to all its instances.
*/
static void ApplyChangesToObjectInstances(
gd::VariablesContainer &objectVariablesContainer,
gd::InitialInstancesContainer &initialInstancesContainer,
const gd::String &objectName, const gd::VariablesChangeset &changeset);
private:
static void ApplyChangesToVariableContainer(
const gd::VariablesContainer &originalVariablesContainer,
gd::VariablesContainer &destinationVariablesContainer,
const gd::VariablesChangeset &changeset, bool shouldApplyValueChanges);
/**
* @brief Apply the changes done on events-based object child to all its
* variants.
*/
static void ApplyChangesToVariants(gd::EventsBasedObject &eventsBasedObject,
const gd::String &objectName,
const gd::VariablesChangeset &changeset);
};
} // namespace gd

View File

@@ -227,12 +227,11 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[this, &instruction](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterExpression,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[this, &instruction](
const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterExpression, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const String& parameterValue = parameterExpression.GetPlainString();
if (parameterMetadata.GetType() == "fontResource") {
gd::String updatedParameterValue = parameterValue;

View File

@@ -314,6 +314,12 @@ void ProjectBrowserHelper::ExposeProjectObjects(
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
auto eventsBasedObject = eventsBasedObjectUniquePtr.get();
worker.Launch(eventsBasedObject->GetObjects());
for (auto &&variantUniquePtr :
eventsBasedObject->GetVariants().GetInternalVector()) {
auto variant = variantUniquePtr.get();
worker.Launch(variant->GetObjects());
}
}
}
};

View File

@@ -248,12 +248,13 @@ gd::String PropertyFunctionGenerator::GetStringifiedExtraInfo(
gd::String arrayString;
arrayString += "[";
bool isFirst = true;
for (const gd::String &choice : property.GetExtraInfo()) {
for (const auto &choice : property.GetChoices()) {
if (!isFirst) {
arrayString += ",";
}
isFirst = false;
arrayString += "\"" + choice + "\"";
// TODO Handle labels (and search "choice label")
arrayString += "\"" + choice.GetValue() + "\"";
}
arrayString += "]";
return arrayString;

View File

@@ -75,6 +75,17 @@ void ResourceExposer::ExposeProjectResources(
// Expose global objects configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
objectWorker.Launch(project.GetObjects());
// Exposed extension event resources
// Note that using resources in extensions is very unlikely and probably not
// worth the effort of something smart.
auto eventWorker = gd::GetResourceWorkerOnEvents(project, worker);
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
project, eventsFunctionsExtension, eventWorker);
}
}
void ResourceExposer::ExposeLayoutResources(
@@ -103,16 +114,6 @@ void ResourceExposer::ExposeLayoutResources(
auto eventWorker = gd::GetResourceWorkerOnEvents(project, worker);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndDependencies(
project, layout, eventWorker);
// Exposed extension event resources
// Note that using resources in extensions is very unlikely and probably not
// worth the effort of something smart.
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
project, eventsFunctionsExtension, eventWorker);
}
}
void ResourceExposer::ExposeEffectResources(

View File

@@ -1781,6 +1781,14 @@ void WholeProjectRefactorer::DoRenameBehavior(
projectBrowser.ExposeFunctions(project, behaviorParameterRenamer);
}
void WholeProjectRefactorer::UpdateBehaviorsSharedData(gd::Project &project) {
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
gd::Layout &layout = project.GetLayout(i);
layout.UpdateBehaviorsSharedData(project);
}
}
void WholeProjectRefactorer::DoRenameObject(
gd::Project &project, const gd::String &oldObjectType,
const gd::String &newObjectType, const gd::ProjectBrowser &projectBrowser) {
@@ -2129,6 +2137,26 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject(
groups[g].RenameObject(oldName, newName);
}
}
for (auto &variant : eventsBasedObject.GetVariants().GetInternalVector()) {
auto &variantObjects = variant->GetObjects();
auto &variantObjectGroups = variantObjects.GetObjectGroups();
if (isObjectGroup) {
if (variantObjectGroups.Has(oldName)) {
variantObjectGroups.Get(oldName).SetName(newName);
}
// Object groups can't have instances or be in other groups
}
else {
if (variantObjects.HasObjectNamed(oldName)) {
variantObjects.GetObject(oldName).SetName(newName);
}
variant->GetInitialInstances().RenameInstancesOfObject(oldName, newName);
for (std::size_t g = 0; g < variantObjectGroups.size(); ++g) {
variantObjectGroups[g].RenameObject(oldName, newName);
}
}
}
}
void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(

View File

@@ -704,6 +704,16 @@ class GD_CORE_API WholeProjectRefactorer {
static size_t GetLayoutAndExternalLayoutLayerInstancesCount(
gd::Project &project, gd::Layout &layout, const gd::String &layerName);
/**
* This ensures that the scenes had an instance of shared data for
* every behavior of every object that can be used on the scene
* (i.e. the objects of the scene and the global objects)
*
* Must be called when a behavior have been added/deleted
* from a global object or an object has been made global.
*/
static void UpdateBehaviorsSharedData(gd::Project &project);
virtual ~WholeProjectRefactorer(){};
private:

View File

@@ -19,6 +19,7 @@ using namespace gd;
void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& objectConfiguration) {
project = objectConfiguration.project;
variantName = objectConfiguration.variantName;
objectContent = objectConfiguration.objectContent;
animations = objectConfiguration.animations;
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
@@ -165,6 +166,7 @@ void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const
animations.SerializeTo(animatableElement);
}
element.SetAttribute("variant", variantName);
if (IsOverridingEventsBasedObjectChildrenConfiguration()) {
auto &childrenContentElement = element.AddChild("childrenContent");
for (auto &pair : childObjectConfigurations) {
@@ -184,6 +186,7 @@ void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
animations.UnserializeFrom(animatableElement);
}
variantName = element.GetStringAttribute("variant");
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
element.HasChild("childrenContent");
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
@@ -247,9 +250,29 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
}
const auto &eventsBasedObject = project->GetEventsBasedObject(GetType());
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
configuration.ExposeResources(worker);
if (IsForcedToOverrideEventsBasedObjectChildrenConfiguration()) {
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
configuration.ExposeResources(worker);
}
}
else if (eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
for (auto &childObject : eventsBasedObject.GetVariants()
.GetVariant(variantName)
.GetObjects()
.GetObjects()) {
childObject->GetConfiguration().ExposeResources(worker);
}
} else if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
configuration.ExposeResources(worker);
}
} else {
for (auto &childObject :
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
childObject->GetConfiguration().ExposeResources(worker);
}
}
}

View File

@@ -29,9 +29,9 @@ namespace gd {
* "resource".
*/
class CustomObjectConfiguration : public gd::ObjectConfiguration {
public:
CustomObjectConfiguration(const Project& project_, const String& type_)
: project(&project_), isMarkedAsOverridingEventsBasedObjectChildrenConfiguration(false) {
public:
CustomObjectConfiguration(const Project &project_, const String &type_)
: project(&project_) {
SetType(type_);
}
std::unique_ptr<gd::ObjectConfiguration> Clone() const override;
@@ -66,6 +66,27 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
void ExposeResources(gd::ArbitraryResourceWorker& worker) override;
/**
* \brief Get the name of the events-based object variant used by this custom object.
*/
const gd::String &GetVariantName() const { return variantName; };
/**
* \brief Set the name of the events-based object variant used by this custom object.
*/
void SetVariantName(const gd::String &variantName_) {
variantName = variantName_;
}
/**
* Legacy events-based objects don't have any instance in their default
* variant since there wasn't a graphical editor at the time. In this case,
* the editor doesn't allow to choose a variant, but a variant may have stayed
* after a user rolled back the extension. This variant must be ignored.
*
* @return true when its events-based object doesn't have any initial
* instance.
*/
bool IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const;
bool IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() const {
@@ -145,6 +166,7 @@ protected:
gd::SerializerElement objectContent;
std::unordered_set<gd::String> unfoldedChildren;
gd::String variantName = "";
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration = false;
mutable std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;

View File

@@ -8,6 +8,8 @@
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
gd::String Effect::badStringParameterValue;
void Effect::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());

View File

@@ -3,8 +3,7 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EFFECT_H
#define GDCORE_EFFECT_H
#pragma once
#include <map>
namespace gd {
class SerializerElement;
@@ -35,28 +34,43 @@ class GD_CORE_API Effect {
void SetFolded(bool fold = true) { folded = fold; }
bool IsFolded() const { return folded; }
void SetDoubleParameter(const gd::String& name, double value) {
void SetDoubleParameter(const gd::String &name, double value) {
doubleParameters[name] = value;
}
double GetDoubleParameter(const gd::String& name) {
return doubleParameters[name];
double GetDoubleParameter(const gd::String &name) const {
auto itr = doubleParameters.find(name);
return itr == doubleParameters.end() ? 0 : itr->second;
}
void SetStringParameter(const gd::String& name, const gd::String& value) {
bool HasDoubleParameter(const gd::String &name) const {
return doubleParameters.find(name) != doubleParameters.end();
}
void SetStringParameter(const gd::String &name, const gd::String &value) {
stringParameters[name] = value;
}
const gd::String& GetStringParameter(const gd::String& name) {
return stringParameters[name];
const gd::String &GetStringParameter(const gd::String &name) const {
auto itr = stringParameters.find(name);
return itr == stringParameters.end() ? badStringParameterValue : itr->second;
}
void SetBooleanParameter(const gd::String& name, bool value) {
bool HasStringParameter(const gd::String &name) const {
return stringParameters.find(name) != stringParameters.end();
}
void SetBooleanParameter(const gd::String &name, bool value) {
booleanParameters[name] = value;
}
bool GetBooleanParameter(const gd::String& name) {
return booleanParameters[name];
bool GetBooleanParameter(const gd::String &name) const {
auto itr = booleanParameters.find(name);
return itr == booleanParameters.end() ? false : itr->second;
}
bool HasBooleanParameter(const gd::String &name) const {
return booleanParameters.find(name) != booleanParameters.end();
}
const std::map<gd::String, double>& GetAllDoubleParameters() const {
@@ -94,7 +108,9 @@ class GD_CORE_API Effect {
std::map<gd::String, double> doubleParameters; ///< Values of parameters being doubles, keyed by names.
std::map<gd::String, gd::String> stringParameters; ///< Values of parameters being strings, keyed by names.
std::map<gd::String, bool> booleanParameters; ///< Values of parameters being booleans, keyed by names.
static gd::String badStringParameterValue; ///< Empty string returned by
///< GeStringParameter
};
} // namespace gd
#endif

View File

@@ -17,19 +17,13 @@ EventsBasedObject::EventsBasedObject()
isAnimatable(false),
isTextContainer(false),
isInnerAreaFollowingParentSize(false),
isUsingLegacyInstancesRenderer(false),
areaMinX(0),
areaMinY(0),
areaMinZ(0),
areaMaxX(64),
areaMaxY(64),
areaMaxZ(64),
objectsContainer(gd::ObjectsContainer::SourceType::Object) {
isUsingLegacyInstancesRenderer(false) {
}
EventsBasedObject::~EventsBasedObject() {}
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
void EventsBasedObject::SerializeToExternal(SerializerElement& element) const {
element.SetAttribute("defaultName", defaultName);
if (isRenderedIn3D) {
element.SetBoolAttribute("is3D", true);
@@ -44,20 +38,16 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
element.SetBoolAttribute("isInnerAreaFollowingParentSize", true);
}
element.SetBoolAttribute("isUsingLegacyInstancesRenderer", isUsingLegacyInstancesRenderer);
element.SetIntAttribute("areaMinX", areaMinX);
element.SetIntAttribute("areaMinY", areaMinY);
element.SetIntAttribute("areaMinZ", areaMinZ);
element.SetIntAttribute("areaMaxX", areaMaxX);
element.SetIntAttribute("areaMaxY", areaMaxY);
element.SetIntAttribute("areaMaxZ", areaMaxZ);
// The EventsBasedObjectVariant SerializeTo method override the name.
// AbstractEventsBasedEntity::SerializeTo must be done after.
defaultVariant.SerializeTo(element);
AbstractEventsBasedEntity::SerializeTo(element);
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
objectsContainer.SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
objectsContainer.GetObjectGroups().SerializeTo(element.AddChild("objectsGroups"));
}
layers.SerializeLayersTo(element.AddChild("layers"));
initialInstances.SerializeTo(element.AddChild("instances"));
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
SerializeToExternal(element);
variants.SerializeVariantsTo(element.AddChild("variants"));
}
void EventsBasedObject::UnserializeFrom(gd::Project& project,
@@ -68,36 +58,22 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project,
isTextContainer = element.GetBoolAttribute("isTextContainer", false);
isInnerAreaFollowingParentSize =
element.GetBoolAttribute("isInnerAreaFollowingParentSize", false);
areaMinX = element.GetIntAttribute("areaMinX", 0);
areaMinY = element.GetIntAttribute("areaMinY", 0);
areaMinZ = element.GetIntAttribute("areaMinZ", 0);
areaMaxX = element.GetIntAttribute("areaMaxX", 64);
areaMaxY = element.GetIntAttribute("areaMaxY", 64);
areaMaxZ = element.GetIntAttribute("areaMaxZ", 64);
defaultVariant.UnserializeFrom(project, element);
defaultVariant.SetName("");
AbstractEventsBasedEntity::UnserializeFrom(project, element);
objectsContainer.UnserializeObjectsFrom(project, element.GetChild("objects"));
if (element.HasChild("objectsFolderStructure")) {
objectsContainer.UnserializeFoldersFrom(project, element.GetChild("objectsFolderStructure", 0));
}
objectsContainer.AddMissingObjectsInRootFolder();
objectsContainer.GetObjectGroups().UnserializeFrom(
element.GetChild("objectsGroups"));
if (element.HasChild("layers")) {
layers.UnserializeLayersFrom(element.GetChild("layers"));
} else {
layers.Reset();
if (element.HasChild("variants")) {
variants.UnserializeVariantsFrom(project, element.GetChild("variants"));
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
if (element.HasChild("isUsingLegacyInstancesRenderer")) {
isUsingLegacyInstancesRenderer =
element.GetBoolAttribute("isUsingLegacyInstancesRenderer", false);
}
else {
// Compatibility with GD <= 5.4.212
isUsingLegacyInstancesRenderer = initialInstances.GetInstancesCount() == 0;
isUsingLegacyInstancesRenderer = GetInitialInstances().GetInstancesCount() == 0;
// end of compatibility code
}
}

View File

@@ -7,6 +7,8 @@
#include <vector>
#include "GDCore/Project/AbstractEventsBasedEntity.h"
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/Project/EventsBasedObjectVariantsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/LayersContainer.h"
@@ -162,18 +164,38 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
*/
bool IsTextContainer() const { return isTextContainer; }
/**
* \brief Get the default variant of the custom object.
*/
const gd::EventsBasedObjectVariant& GetDefaultVariant() const { return defaultVariant; }
/**
* \brief Get the default variant of the custom object.
*/
gd::EventsBasedObjectVariant& GetDefaultVariant() { return defaultVariant; }
/**
* \brief Get the variants of the custom object.
*/
const gd::EventsBasedObjectVariantsContainer& GetVariants() const { return variants; }
/**
* \brief Get the variants of the custom object.
*/
gd::EventsBasedObjectVariantsContainer& GetVariants() { return variants; }
/** \name Layers
*/
///@{
/**
* \brief Get the layers of the custom object.
*/
const gd::LayersContainer& GetLayers() const { return layers; }
const gd::LayersContainer& GetLayers() const { return defaultVariant.GetLayers(); }
/**
* \brief Get the layers of the custom object.
*/
gd::LayersContainer& GetLayers() { return layers; }
gd::LayersContainer& GetLayers() { return defaultVariant.GetLayers(); }
///@}
/** \name Child objects
@@ -183,14 +205,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \brief Get the objects of the custom object.
*/
gd::ObjectsContainer& GetObjects() {
return objectsContainer;
return defaultVariant.GetObjects();
}
/**
* \brief Get the objects of the custom object.
*/
const gd::ObjectsContainer& GetObjects() const {
return objectsContainer;
return defaultVariant.GetObjects();
}
///@}
@@ -201,14 +223,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \brief Get the instances of the custom object.
*/
gd::InitialInstancesContainer& GetInitialInstances() {
return initialInstances;
return defaultVariant.GetInitialInstances();
}
/**
* \brief Get the instances of the custom object.
*/
const gd::InitialInstancesContainer& GetInitialInstances() const {
return initialInstances;
return defaultVariant.GetInitialInstances();
}
/**
@@ -219,14 +241,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMinX() const {
return areaMinX;
return defaultVariant.GetAreaMinX();
}
/**
* \brief Set the left bound of the custom object.
*/
void SetAreaMinX(int areaMinX_) {
areaMinX = areaMinX_;
void SetAreaMinX(int areaMinX) {
defaultVariant.SetAreaMinX(areaMinX);
}
/**
@@ -237,14 +259,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMinY() const {
return areaMinY;
return defaultVariant.GetAreaMinY();
}
/**
* \brief Set the top bound of the custom object.
*/
void SetAreaMinY(int areaMinY_) {
areaMinY = areaMinY_;
void SetAreaMinY(int areaMinY) {
defaultVariant.SetAreaMinY(areaMinY);
}
/**
@@ -255,14 +277,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMinZ() const {
return areaMinZ;
return defaultVariant.GetAreaMinZ();
}
/**
* \brief Set the min Z bound of the custom object.
*/
void SetAreaMinZ(int areaMinZ_) {
areaMinZ = areaMinZ_;
void SetAreaMinZ(int areaMinZ) {
defaultVariant.SetAreaMinZ(areaMinZ);
}
/**
@@ -273,14 +295,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMaxX() const {
return areaMaxX;
return defaultVariant.GetAreaMaxX();
}
/**
* \brief Set the right bound of the custom object.
*/
void SetAreaMaxX(int areaMaxX_) {
areaMaxX = areaMaxX_;
void SetAreaMaxX(int areaMaxX) {
defaultVariant.SetAreaMaxX(areaMaxX);
}
/**
@@ -291,14 +313,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMaxY() const {
return areaMaxY;
return defaultVariant.GetAreaMaxY();
}
/**
* \brief Set the bottom bound of the custom object.
*/
void SetAreaMaxY(int areaMaxY_) {
areaMaxY = areaMaxY_;
void SetAreaMaxY(int areaMaxY) {
defaultVariant.SetAreaMaxY(areaMaxY);
}
/**
@@ -309,16 +331,22 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMaxZ() const {
return areaMaxZ;
return defaultVariant.GetAreaMaxZ();
}
/**
* \brief Set the bottom bound of the custom object.
*/
void SetAreaMaxZ(int areaMaxZ_) {
areaMaxZ = areaMaxZ_;
void SetAreaMaxZ(int areaMaxZ) {
defaultVariant.SetAreaMaxZ(areaMaxZ);
}
///@}
/**
* @brief Serialize the events-based object for an extension in an external file.
* Variants are not serialized.
*/
void SerializeToExternal(SerializerElement& element) const;
void SerializeTo(SerializerElement& element) const override;
@@ -332,15 +360,8 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
bool isTextContainer;
bool isInnerAreaFollowingParentSize;
bool isUsingLegacyInstancesRenderer;
gd::InitialInstancesContainer initialInstances;
gd::LayersContainer layers;
gd::ObjectsContainer objectsContainer;
double areaMinX;
double areaMinY;
double areaMinZ;
double areaMaxX;
double areaMaxY;
double areaMaxZ;
gd::EventsBasedObjectVariant defaultVariant;
gd::EventsBasedObjectVariantsContainer variants;
};
} // namespace gd

View File

@@ -0,0 +1,71 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "EventsBasedObjectVariant.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
EventsBasedObjectVariant::EventsBasedObjectVariant()
: areaMinX(0), areaMinY(0), areaMinZ(0), areaMaxX(64), areaMaxY(64),
areaMaxZ(64), objectsContainer(gd::ObjectsContainer::SourceType::Object) {
}
EventsBasedObjectVariant::~EventsBasedObjectVariant() {}
void EventsBasedObjectVariant::SerializeTo(SerializerElement &element) const {
element.SetAttribute("name", name);
if (!GetAssetStoreAssetId().empty() && !GetAssetStoreOriginalName().empty()) {
element.SetAttribute("assetStoreAssetId", GetAssetStoreAssetId());
element.SetAttribute("assetStoreOriginalName", GetAssetStoreOriginalName());
}
element.SetIntAttribute("areaMinX", areaMinX);
element.SetIntAttribute("areaMinY", areaMinY);
element.SetIntAttribute("areaMinZ", areaMinZ);
element.SetIntAttribute("areaMaxX", areaMaxX);
element.SetIntAttribute("areaMaxY", areaMaxY);
element.SetIntAttribute("areaMaxZ", areaMaxZ);
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
objectsContainer.SerializeFoldersTo(
element.AddChild("objectsFolderStructure"));
objectsContainer.GetObjectGroups().SerializeTo(
element.AddChild("objectsGroups"));
layers.SerializeLayersTo(element.AddChild("layers"));
initialInstances.SerializeTo(element.AddChild("instances"));
}
void EventsBasedObjectVariant::UnserializeFrom(
gd::Project &project, const SerializerElement &element) {
name = element.GetStringAttribute("name");
assetStoreAssetId = element.GetStringAttribute("assetStoreAssetId");
assetStoreOriginalName = element.GetStringAttribute("assetStoreOriginalName");
areaMinX = element.GetIntAttribute("areaMinX", 0);
areaMinY = element.GetIntAttribute("areaMinY", 0);
areaMinZ = element.GetIntAttribute("areaMinZ", 0);
areaMaxX = element.GetIntAttribute("areaMaxX", 64);
areaMaxY = element.GetIntAttribute("areaMaxY", 64);
areaMaxZ = element.GetIntAttribute("areaMaxZ", 64);
objectsContainer.UnserializeObjectsFrom(project, element.GetChild("objects"));
if (element.HasChild("objectsFolderStructure")) {
objectsContainer.UnserializeFoldersFrom(
project, element.GetChild("objectsFolderStructure", 0));
}
objectsContainer.AddMissingObjectsInRootFolder();
objectsContainer.GetObjectGroups().UnserializeFrom(
element.GetChild("objectsGroups"));
if (element.HasChild("layers")) {
layers.UnserializeLayersFrom(element.GetChild("layers"));
} else {
layers.Reset();
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
}
} // namespace gd

View File

@@ -0,0 +1,229 @@
/*
* GDevelop Core
* Copyright 2008-2025 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/LayersContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/String.h"
#include <vector>
namespace gd {
class SerializerElement;
class Project;
} // namespace gd
namespace gd {
/**
* \brief Represents a variation of style of an events-based object.
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsBasedObjectVariant {
public:
EventsBasedObjectVariant();
virtual ~EventsBasedObjectVariant();
/**
* \brief Return a pointer to a new EventsBasedObjectVariant constructed from
* this one.
*/
EventsBasedObjectVariant *Clone() const {
return new EventsBasedObjectVariant(*this);
};
/**
* \brief Get the name of the variant.
*/
const gd::String &GetName() const { return name; };
/**
* \brief Set the name of the variant.
*/
EventsBasedObjectVariant &SetName(const gd::String &name_) {
name = name_;
return *this;
}
/** \name Layers
*/
///@{
/**
* \brief Get the layers of the variant.
*/
const gd::LayersContainer &GetLayers() const { return layers; }
/**
* \brief Get the layers of the variant.
*/
gd::LayersContainer &GetLayers() { return layers; }
///@}
/** \name Child objects
*/
///@{
/**
* \brief Get the objects of the variant.
*/
gd::ObjectsContainer &GetObjects() { return objectsContainer; }
/**
* \brief Get the objects of the variant.
*/
const gd::ObjectsContainer &GetObjects() const { return objectsContainer; }
///@}
/** \name Instances
*/
///@{
/**
* \brief Get the instances of the variant.
*/
gd::InitialInstancesContainer &GetInitialInstances() {
return initialInstances;
}
/**
* \brief Get the instances of the variant.
*/
const gd::InitialInstancesContainer &GetInitialInstances() const {
return initialInstances;
}
/**
* \brief Get the left bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMinX() const { return areaMinX; }
/**
* \brief Set the left bound of the variant.
*/
void SetAreaMinX(int areaMinX_) { areaMinX = areaMinX_; }
/**
* \brief Get the top bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMinY() const { return areaMinY; }
/**
* \brief Set the top bound of the variant.
*/
void SetAreaMinY(int areaMinY_) { areaMinY = areaMinY_; }
/**
* \brief Get the min Z bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMinZ() const { return areaMinZ; }
/**
* \brief Set the min Z bound of the variant.
*/
void SetAreaMinZ(int areaMinZ_) { areaMinZ = areaMinZ_; }
/**
* \brief Get the right bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMaxX() const { return areaMaxX; }
/**
* \brief Set the right bound of the variant.
*/
void SetAreaMaxX(int areaMaxX_) { areaMaxX = areaMaxX_; }
/**
* \brief Get the bottom bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMaxY() const { return areaMaxY; }
/**
* \brief Set the bottom bound of the variant.
*/
void SetAreaMaxY(int areaMaxY_) { areaMaxY = areaMaxY_; }
/**
* \brief Get the max Z bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMaxZ() const { return areaMaxZ; }
/**
* \brief Set the bottom bound of the variant.
*/
void SetAreaMaxZ(int areaMaxZ_) { areaMaxZ = areaMaxZ_; }
///@}
/** \brief Change the object asset store id of this variant.
*/
void SetAssetStoreAssetId(const gd::String &assetStoreId_) {
assetStoreAssetId = assetStoreId_;
};
/** \brief Return the object asset store id of this variant.
*/
const gd::String &GetAssetStoreAssetId() const { return assetStoreAssetId; };
/** \brief Change the original name of the variant in the asset.
*/
void SetAssetStoreOriginalName(const gd::String &assetStoreOriginalName_) {
assetStoreOriginalName = assetStoreOriginalName_;
};
/** \brief Return the original name of the variant in the asset.
*/
const gd::String &GetAssetStoreOriginalName() const {
return assetStoreOriginalName;
};
void SerializeTo(SerializerElement &element) const;
void UnserializeFrom(gd::Project &project, const SerializerElement &element);
private:
gd::String name;
gd::InitialInstancesContainer initialInstances;
gd::LayersContainer layers;
gd::ObjectsContainer objectsContainer;
double areaMinX;
double areaMinY;
double areaMinZ;
double areaMaxX;
double areaMaxY;
double areaMaxZ;
/**
* The ID of the asset if the object comes from the store.
*/
gd::String assetStoreAssetId;
/**
* The original name of the variant in the asset if the object comes from the
* store.
*/
gd::String assetStoreOriginalName;
};
} // namespace gd

View File

@@ -0,0 +1,160 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <vector>
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
namespace gd {
class SerializerElement;
}
namespace gd {
/**
* \brief Used as a base class for classes that will own events-backed
* variants.
*
* \see gd::EventsBasedObjectVariantContainer
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsBasedObjectVariantsContainer
: private SerializableWithNameList<gd::EventsBasedObjectVariant> {
public:
EventsBasedObjectVariantsContainer() {}
EventsBasedObjectVariantsContainer(const EventsBasedObjectVariantsContainer &other) {
Init(other);
}
EventsBasedObjectVariantsContainer &operator=(const EventsBasedObjectVariantsContainer &other) {
if (this != &other) {
Init(other);
}
return *this;
}
/** \name Events Functions management
*/
///@{
/**
* \brief Check if the variant with the specified name exists.
*/
bool HasVariantNamed(const gd::String& name) const {
return Has(name);
}
/**
* \brief Get the variant with the specified name.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
gd::EventsBasedObjectVariant& GetVariant(const gd::String& name) {
return Get(name);
}
/**
* \brief Get the variant with the specified name.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
const gd::EventsBasedObjectVariant& GetVariant(const gd::String& name) const {
return Get(name);
}
/**
* \brief Get the variant at the specified index in the list.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
gd::EventsBasedObjectVariant& GetVariant(std::size_t index) {
return Get(index);
}
/**
* \brief Get the variant at the specified index in the list.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
const gd::EventsBasedObjectVariant& GetVariant(std::size_t index) const {
return Get(index);
}
/**
* \brief Return the number of variants.
*/
std::size_t GetVariantsCount() const { return GetCount(); }
gd::EventsBasedObjectVariant& InsertNewVariant(const gd::String& name,
std::size_t position) {
return InsertNew(name, position);
}
gd::EventsBasedObjectVariant& InsertVariant(const gd::EventsBasedObjectVariant& object,
std::size_t position) {
return Insert(object, position);
}
void RemoveVariant(const gd::String& name) { return Remove(name); }
void ClearVariants() { return Clear(); }
void MoveVariant(std::size_t oldIndex, std::size_t newIndex) {
return Move(oldIndex, newIndex);
};
std::size_t GetVariantPosition(const gd::EventsBasedObjectVariant& eventsFunction) {
return GetPosition(eventsFunction);
};
/**
* \brief Provide a raw access to the vector containing the variants.
*/
const std::vector<std::unique_ptr<gd::EventsBasedObjectVariant>>& GetInternalVector()
const {
return elements;
};
/**
* \brief Provide a raw access to the vector containing the variants.
*/
std::vector<std::unique_ptr<gd::EventsBasedObjectVariant>>& GetInternalVector() {
return elements;
};
///@}
/** \name Serialization
*/
///@{
/**
* \brief Serialize events variants.
*/
void SerializeVariantsTo(SerializerElement& element) const {
return SerializeElementsTo("variant", element);
};
/**
* \brief Unserialize the events variants.
*/
void UnserializeVariantsFrom(gd::Project& project,
const SerializerElement& element) {
return UnserializeElementsFrom("variant", project, element);
};
///@}
protected:
/**
* Initialize object using another object. Used by copy-ctor and assign-op.
* Don't forget to update me if members were changed!
*/
void Init(const gd::EventsBasedObjectVariantsContainer& other) {
return SerializableWithNameList<gd::EventsBasedObjectVariant>::Init(other);
};
private:
};
} // namespace gd

View File

@@ -55,7 +55,7 @@ void EventsFunctionsExtension::Init(const gd::EventsFunctionsExtension& other) {
sceneVariables = other.GetSceneVariables();
}
void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
void EventsFunctionsExtension::SerializeTo(SerializerElement& element, bool isExternal) const {
element.SetAttribute("version", version);
element.SetAttribute("extensionNamespace", extensionNamespace);
element.SetAttribute("shortDescription", shortDescription);
@@ -83,6 +83,9 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
element.SetAttribute("iconUrl", iconUrl);
element.SetAttribute("helpPath", helpPath);
element.SetAttribute("gdevelopVersion", gdevelopVersion);
if (changelog.GetChangesCount() > 0) {
changelog.SerializeTo(element.AddChild("changelog"));
}
auto& dependenciesElement = element.AddChild("dependencies");
dependenciesElement.ConsiderAsArray();
for (auto& dependency : dependencies)
@@ -102,8 +105,18 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
element.AddChild("eventsFunctions"));
eventsBasedBehaviors.SerializeElementsTo(
"eventsBasedBehavior", element.AddChild("eventsBasedBehaviors"));
eventsBasedObjects.SerializeElementsTo(
"eventsBasedObject", element.AddChild("eventsBasedObjects"));
if (isExternal) {
auto &eventsBasedObjectElement = element.AddChild("eventsBasedObjects");
eventsBasedObjectElement.ConsiderAsArrayOf("eventsBasedObject");
for (const auto &eventsBasedObject :
eventsBasedObjects.GetInternalVector()) {
eventsBasedObject->SerializeToExternal(
eventsBasedObjectElement.AddChild("eventsBasedObject"));
}
} else {
eventsBasedObjects.SerializeElementsTo(
"eventsBasedObject", element.AddChild("eventsBasedObjects"));
}
}
void EventsFunctionsExtension::UnserializeFrom(
@@ -129,6 +142,9 @@ void EventsFunctionsExtension::UnserializeExtensionDeclarationFrom(
iconUrl = element.GetStringAttribute("iconUrl");
helpPath = element.GetStringAttribute("helpPath");
gdevelopVersion = element.GetStringAttribute("gdevelopVersion");
if (element.HasChild("changelog")) {
changelog.UnserializeFrom(element.GetChild("changelog"));
}
if (element.HasChild("origin")) {
gd::String originName =

View File

@@ -12,9 +12,11 @@
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/Project/EventsFunctionsExtensionChangelog.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
namespace gd {
class SerializerElement;
class Project;
@@ -286,7 +288,14 @@ class GD_CORE_API EventsFunctionsExtension {
/**
* \brief Serialize the EventsFunctionsExtension to the specified element
*/
void SerializeTo(gd::SerializerElement& element) const;
void SerializeTo(gd::SerializerElement& element, bool isExternal = false) const;
/**
* \brief Serialize the EventsFunctionsExtension to the specified element
*/
void SerializeToExternal(gd::SerializerElement& element) const {
SerializeTo(element, true);
}
/**
* \brief Load the EventsFunctionsExtension from the specified element.
@@ -399,6 +408,7 @@ class GD_CORE_API EventsFunctionsExtension {
gd::String helpPath; ///< The relative path to the help for this extension in
///< the documentation (or an absolute URL).
gd::String gdevelopVersion;
gd::EventsFunctionsExtensionChangelog changelog;
gd::SerializableWithNameList<EventsBasedBehavior> eventsBasedBehaviors;
gd::SerializableWithNameList<EventsBasedObject> eventsBasedObjects;
std::vector<gd::DependencyMetadata> dependencies;

View File

@@ -0,0 +1,105 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <vector>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
namespace gd {
/**
* @brief The change of a specific extension version (only the breaking
* changes).
*/
class GD_CORE_API EventsFunctionsExtensionVersionChange {
public:
EventsFunctionsExtensionVersionChange(){};
virtual ~EventsFunctionsExtensionVersionChange(){};
const gd::String &GetVersion() const { return version; };
gd::EventsFunctionsExtensionVersionChange &
SetVersion(const gd::String &version_) {
version = version_;
return *this;
}
const gd::String &GetBreakingChangesDescription() const { return version; };
gd::EventsFunctionsExtensionVersionChange &
GetBreakingChangesDescription(const gd::String &breakingChangesDescription_) {
breakingChangesDescription = breakingChangesDescription_;
return *this;
}
/**
* \brief Serialize the EventsFunctionsExtensionVersionChange to the specified
* element
*/
void SerializeTo(gd::SerializerElement &element) const {
element.SetAttribute("version", version);
element.AddChild("breaking")
.SetMultilineStringValue(breakingChangesDescription);
}
/**
* \brief Load the EventsFunctionsExtensionVersionChange from the specified
* element.
*/
void UnserializeFrom(const gd::SerializerElement &element) {
version = element.GetStringAttribute("version");
breakingChangesDescription =
element.GetChild("breaking").GetMultilineStringValue();
}
private:
gd::String version;
gd::String breakingChangesDescription;
};
/**
* @brief The changelog of an extension (only the breaking changes).
*/
class GD_CORE_API EventsFunctionsExtensionChangelog {
public:
EventsFunctionsExtensionChangelog(){};
virtual ~EventsFunctionsExtensionChangelog(){};
/**
* \brief Return the number of variants.
*/
std::size_t GetChangesCount() const { return versionChanges.size(); }
/**
* \brief Serialize the EventsFunctionsExtensionChangelog to the specified
* element
*/
void SerializeTo(gd::SerializerElement &element) const {
element.ConsiderAsArray();
for (const auto &versionChange : versionChanges) {
versionChange.SerializeTo(element.AddChild(""));
}
}
/**
* \brief Load the EventsFunctionsExtensionChangelog from the specified
* element.
*/
void UnserializeFrom(const gd::SerializerElement &element) {
versionChanges.clear();
element.ConsiderAsArray();
for (std::size_t i = 0; i < element.GetChildrenCount(); ++i) {
gd::EventsFunctionsExtensionVersionChange versionChange;
versionChange.UnserializeFrom(element.GetChild(i));
versionChanges.push_back(versionChange);
}
}
private:
std::vector<gd::EventsFunctionsExtensionVersionChange> versionChanges;
};
} // namespace gd

View File

@@ -365,6 +365,8 @@ class GD_CORE_API InitialInstance {
* the same initial instance between serialization.
*/
InitialInstance& ResetPersistentUuid();
const gd::String& GetPersistentUuid() const { return persistentUuid; }
///@}
private:

View File

@@ -42,8 +42,13 @@ void InitialInstancesContainer::IterateOverInstances(
}
void InitialInstancesContainer::IterateOverInstances(
const std::function< void(gd::InitialInstance &) >& func) {
for (auto& instance : initialInstances) func(instance);
const std::function< bool(gd::InitialInstance &) >& func) {
for (auto& instance : initialInstances) {
bool shouldStop = func(instance);
if (shouldStop) {
return;
}
}
}
void InitialInstancesContainer::IterateOverInstancesWithZOrdering(

View File

@@ -92,7 +92,7 @@ class GD_CORE_API InitialInstancesContainer {
* \see InitialInstanceFunctor
*/
void IterateOverInstances(
const std::function< void(gd::InitialInstance &) >& func);
const std::function< bool(gd::InitialInstance &) >& func);
/**
* Get the instances on the specified layer,

View File

@@ -36,7 +36,7 @@ namespace gd {
gd::BehaviorsSharedData Layout::badBehaviorSharedData("", "");
Layout::Layout(const Layout &other)
Layout::Layout(const Layout& other)
: objectsContainer(gd::ObjectsContainer::SourceType::Scene) {
Init(other);
}
@@ -54,6 +54,8 @@ Layout::Layout()
backgroundColorG(209),
backgroundColorB(209),
stopSoundsOnStartup(true),
resourcesPreloading("inherit"),
resourcesUnloading("inherit"),
standardSortMethod(true),
disableInputWhenNotFocused(true),
variables(gd::VariablesContainer::SourceType::Scene),
@@ -244,6 +246,10 @@ void Layout::SerializeTo(SerializerElement& element) const {
element.SetAttribute("title", GetWindowDefaultTitle());
element.SetAttribute("standardSortMethod", standardSortMethod);
element.SetAttribute("stopSoundsOnStartup", stopSoundsOnStartup);
if (resourcesPreloading != "inherit")
element.SetAttribute("resourcesPreloading", resourcesPreloading);
if (resourcesUnloading != "inherit")
element.SetAttribute("resourcesUnloading", resourcesUnloading);
element.SetAttribute("disableInputWhenNotFocused",
disableInputWhenNotFocused);
@@ -304,6 +310,10 @@ void Layout::UnserializeFrom(gd::Project& project,
element.GetStringAttribute("title", "(No title)", "titre"));
standardSortMethod = element.GetBoolAttribute("standardSortMethod");
stopSoundsOnStartup = element.GetBoolAttribute("stopSoundsOnStartup");
resourcesPreloading =
element.GetStringAttribute("resourcesPreloading", "inherit");
resourcesUnloading =
element.GetStringAttribute("resourcesUnloading", "inherit");
disableInputWhenNotFocused =
element.GetBoolAttribute("disableInputWhenNotFocused");
@@ -391,6 +401,8 @@ void Layout::Init(const Layout& other) {
standardSortMethod = other.standardSortMethod;
title = other.title;
stopSoundsOnStartup = other.stopSoundsOnStartup;
resourcesPreloading = other.resourcesPreloading;
resourcesUnloading = other.resourcesUnloading;
disableInputWhenNotFocused = other.disableInputWhenNotFocused;
initialInstances = other.initialInstances;
layers = other.layers;

View File

@@ -349,6 +349,36 @@ class GD_CORE_API Layout {
* launched
*/
bool StopSoundsOnStartup() const { return stopSoundsOnStartup; }
/**
* Set when the scene must preload its resources: `at-startup`, `never` or
* `inherit` (default).
*/
void SetResourcesPreloading(gd::String resourcesPreloading_) {
resourcesPreloading = resourcesPreloading_;
}
/**
* Get when the scene must preload its resources: `at-startup`, `never` or
* `inherit` (default).
*/
const gd::String& GetResourcesPreloading() const {
return resourcesPreloading;
}
/**
* Set when the scene must unload its resources: `at-scene-exit`, `never` or
* `inherit` (default).
*/
void SetResourcesUnloading(gd::String resourcesUnloading_) {
resourcesUnloading = resourcesUnloading_;
}
/**
* Get when the scene must unload its resources: `at-scene-exit`, `never` or
* `inherit` (default).
*/
const gd::String& GetResourcesUnloading() const { return resourcesUnloading; }
///@}
/** \name Saving and loading
@@ -381,6 +411,10 @@ class GD_CORE_API Layout {
behaviorsSharedData; ///< Initial shared datas of behaviors
bool stopSoundsOnStartup = true; ///< True to make the scene stop all sounds at
///< startup.
gd::String
resourcesPreloading; ///< `at-startup`, `never` or `inherit` (default).
gd::String
resourcesUnloading; ///< `at-scene-exit`, `never` or `inherit` (default).
bool standardSortMethod = true; ///< True to sort objects using standard sort.
bool disableInputWhenNotFocused = true; /// If set to true, the input must be
/// disabled when the window do not have the

View File

@@ -41,6 +41,11 @@ Object::Object(const gd::String& name_,
}
void Object::Init(const gd::Object& object) {
CopyWithoutConfiguration(object);
configuration = object.configuration->Clone();
}
void Object::CopyWithoutConfiguration(const gd::Object& object) {
persistentUuid = object.persistentUuid;
name = object.name;
assetStoreId = object.assetStoreId;
@@ -51,8 +56,6 @@ void Object::Init(const gd::Object& object) {
for (auto& it : object.behaviors) {
behaviors[it.first] = gd::make_unique<gd::Behavior>(*it.second);
}
configuration = object.configuration->Clone();
}
gd::ObjectConfiguration& Object::GetConfiguration() { return *configuration; }

View File

@@ -82,6 +82,8 @@ class GD_CORE_API Object {
return gd::make_unique<gd::Object>(*this);
}
void CopyWithoutConfiguration(const gd::Object& object);
/**
* \brief Return the object configuration.
*/

View File

@@ -81,6 +81,11 @@ class GD_CORE_API ObjectsContainersList {
/**
* \brief Return the container of the variables for the specified object or
* group of objects.
*
* \warning In most cases, prefer to use other methods to access variables or use
* ObjectVariableHelper::MergeVariableContainers if you know you're dealing with a group.
* This is because the variables container of an object group does not exist and the one from
* first object of the group will be returned.
*/
const gd::VariablesContainer* GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) const;

View File

@@ -74,7 +74,9 @@ Project::Project()
gdMinorVersion(gd::VersionWrapper::Minor()),
gdBuildVersion(gd::VersionWrapper::Build()),
variables(gd::VariablesContainer::SourceType::Global),
objectsContainer(gd::ObjectsContainer::SourceType::Global) {}
objectsContainer(gd::ObjectsContainer::SourceType::Global),
sceneResourcesPreloading("at-startup"),
sceneResourcesUnloading("never") {}
Project::~Project() {}
@@ -920,6 +922,7 @@ void Project::UnserializeAndInsertExtensionsFrom(
"eventsFunctionsExtension");
std::map<gd::String, size_t> extensionNameToElementIndex;
std::map<gd::String, gd::SerializerElement> objectTypeToVariantsElement;
// First, only unserialize behaviors and objects names.
// As event based objects can contains custom behaviors and custom objects,
@@ -938,6 +941,16 @@ void Project::UnserializeAndInsertExtensionsFrom(
? GetEventsFunctionsExtension(name)
: InsertNewEventsFunctionsExtension(
name, GetEventsFunctionsExtensionsCount());
// Backup the events-based object variants
for (auto &eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
gd::SerializerElement variantsElement;
eventsBasedObject->GetVariants().SerializeVariantsTo(variantsElement);
objectTypeToVariantsElement[gd::PlatformExtension::GetObjectFullType(
name, eventsBasedObject->GetName())] = variantsElement;
}
eventsFunctionsExtension.UnserializeExtensionDeclarationFrom(
*this, eventsFunctionsExtensionElement);
}
@@ -966,6 +979,15 @@ void Project::UnserializeAndInsertExtensionsFrom(
partiallyLoadedExtension
->UnserializeExtensionImplementationFrom(
*this, eventsFunctionsExtensionElement);
for (auto &pair : objectTypeToVariantsElement) {
auto &objectType = pair.first;
auto &variantsElement = pair.second;
auto &eventsBasedObject = GetEventsBasedObject(objectType);
eventsBasedObject.GetVariants().UnserializeVariantsFrom(*this,
variantsElement);
}
}
}
@@ -1146,6 +1168,13 @@ void Project::SerializeTo(SerializerElement& element) const {
else
std::cout << "ERROR: The project current platform is NULL.";
if (sceneResourcesPreloading != "at-startup") {
propElement.SetAttribute("sceneResourcesPreloading", sceneResourcesPreloading);
}
if (sceneResourcesUnloading != "never") {
propElement.SetAttribute("sceneResourcesUnloading", sceneResourcesUnloading);
}
resourcesManager.SerializeTo(element.AddChild("resources"));
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
objectsContainer.SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
@@ -1287,6 +1316,9 @@ void Project::Init(const gd::Project& game) {
variables = game.GetVariables();
projectFile = game.GetProjectFile();
sceneResourcesPreloading = game.sceneResourcesPreloading;
sceneResourcesUnloading = game.sceneResourcesUnloading;
}
} // namespace gd

View File

@@ -964,6 +964,37 @@ class GD_CORE_API Project {
*/
ResourcesManager& GetResourcesManager() { return resourcesManager; }
/**
* Set when the scenes must preload their resources: `at-startup`, `never`
* (default).
*/
void SetSceneResourcesPreloading(gd::String sceneResourcesPreloading_) {
sceneResourcesPreloading = sceneResourcesPreloading_;
}
/**
* Get when the scenes must preload their resources: `at-startup`, `never`
* (default).
*/
const gd::String& GetSceneResourcesPreloading() const {
return sceneResourcesPreloading;
}
/**
* Set when the scenes must unload their resources: `at-scene-exit`, `never`
* (default).
*/
void SetSceneResourcesUnloading(gd::String sceneResourcesUnloading_) {
sceneResourcesUnloading = sceneResourcesUnloading_;
}
/**
* Get when the scenes must unload their resources: `at-scene-exit`, `never`
* (default).
*/
const gd::String& GetSceneResourcesUnloading() const {
return sceneResourcesUnloading;
}
///@}
/** \name Variable management
@@ -1121,6 +1152,10 @@ class GD_CORE_API Project {
ExtensionProperties
extensionProperties; ///< The properties of the extensions.
gd::WholeProjectDiagnosticReport wholeProjectDiagnosticReport;
gd::String sceneResourcesPreloading; ///< `at-startup` or `never`
///< (default: `at-startup`).
gd::String sceneResourcesUnloading; ///< `at-scene-exit` or `never`
///< (default: `never`).
mutable unsigned int gdMajorVersion =
0; ///< The GD major version used the last
///< time the project was saved.

View File

@@ -21,14 +21,33 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
element.AddChild("unit").SetStringValue(measurementUnit.GetName());
}
element.AddChild("label").SetStringValue(label);
element.AddChild("description").SetStringValue(description);
element.AddChild("group").SetStringValue(group);
SerializerElement& extraInformationElement =
element.AddChild("extraInformation");
extraInformationElement.ConsiderAsArray();
for (const gd::String& information : extraInformation) {
extraInformationElement.AddChild("").SetStringValue(information);
if (!description.empty())
element.AddChild("description").SetStringValue(description);
if (!group.empty()) element.AddChild("group").SetStringValue(group);
if (!extraInformation.empty()) {
SerializerElement& extraInformationElement =
element.AddChild("extraInformation");
extraInformationElement.ConsiderAsArray();
for (const gd::String& information : extraInformation) {
extraInformationElement.AddChild("").SetStringValue(information);
}
}
if (!choices.empty()
// Compatibility with GD <= 5.5.239
|| !extraInformation.empty()
// end of compatibility code
) {
SerializerElement &choicesElement = element.AddChild("choices");
choicesElement.ConsiderAsArrayOf("choice");
for (const auto &choice : choices) {
auto &choiceElement = choicesElement.AddChild("Choice");
choiceElement.SetStringAttribute("value", choice.GetValue());
choiceElement.SetStringAttribute("label", choice.GetLabel());
}
}
if (hidden) {
element.AddChild("hidden").SetBoolValue(hidden);
}
@@ -59,16 +78,41 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
: gd::MeasurementUnit::GetUndefined();
}
label = element.GetChild("label").GetStringValue();
description = element.GetChild("description").GetStringValue();
group = element.GetChild("group").GetStringValue();
description = element.HasChild("description")
? element.GetChild("description").GetStringValue()
: "";
group = element.HasChild("group") ? element.GetChild("group").GetStringValue()
: "";
extraInformation.clear();
const SerializerElement& extraInformationElement =
element.GetChild("extraInformation");
extraInformationElement.ConsiderAsArray();
for (std::size_t i = 0; i < extraInformationElement.GetChildrenCount(); ++i)
extraInformation.push_back(
extraInformationElement.GetChild(i).GetStringValue());
if (element.HasChild("extraInformation")) {
const SerializerElement& extraInformationElement =
element.GetChild("extraInformation");
extraInformationElement.ConsiderAsArray();
for (std::size_t i = 0; i < extraInformationElement.GetChildrenCount(); ++i)
extraInformation.push_back(
extraInformationElement.GetChild(i).GetStringValue());
}
if (element.HasChild("choices")) {
choices.clear();
const SerializerElement &choicesElement = element.GetChild("choices");
choicesElement.ConsiderAsArrayOf("choice");
for (std::size_t i = 0; i < choicesElement.GetChildrenCount(); ++i) {
auto &choiceElement = choicesElement.GetChild(i);
AddChoice(choiceElement.GetStringAttribute("value"),
choiceElement.GetStringAttribute("label"));
}
}
// Compatibility with GD <= 5.5.239
else if (type == "Choice") {
choices.clear();
for (auto &choiceValue : extraInformation) {
AddChoice(choiceValue, choiceValue);
}
extraInformation.clear();
}
// end of compatibility code
hidden = element.HasChild("hidden")
? element.GetChild("hidden").GetBoolValue()

View File

@@ -7,9 +7,9 @@
#define GDCORE_PROPERTYDESCRIPTOR
#include <vector>
#include "GDCore/String.h"
#include "GDCore/Project/MeasurementUnit.h"
#include "GDCore/Project/QuickCustomization.h"
#include "GDCore/String.h"
namespace gd {
class SerializerElement;
@@ -17,6 +17,19 @@ class SerializerElement;
namespace gd {
class GD_CORE_API PropertyDescriptorChoice {
public:
PropertyDescriptorChoice(const gd::String& value, const gd::String& label)
: value(value), label(label) {}
const gd::String& GetValue() const { return value; }
const gd::String& GetLabel() const { return label; }
private:
gd::String value;
gd::String label;
};
/**
* \brief Used to describe a property shown in a property grid.
* \see gd::Object
@@ -31,8 +44,12 @@ class GD_CORE_API PropertyDescriptor {
* \param propertyValue The value of the property.
*/
PropertyDescriptor(gd::String propertyValue)
: currentValue(propertyValue), type("string"), label(""), hidden(false),
deprecated(false), advanced(false),
: currentValue(propertyValue),
type("string"),
label(""),
hidden(false),
deprecated(false),
advanced(false),
hasImpactOnOtherProperties(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
@@ -41,10 +58,13 @@ class GD_CORE_API PropertyDescriptor {
* \brief Empty constructor creating an empty property to be displayed.
*/
PropertyDescriptor()
: hidden(false), deprecated(false), advanced(false),
: hidden(false),
deprecated(false),
advanced(false),
hasImpactOnOtherProperties(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {
};
/**
* \brief Destructor
@@ -88,13 +108,25 @@ class GD_CORE_API PropertyDescriptor {
}
/**
* \brief Change the group where this property is displayed to the user, if any.
* \brief Change the group where this property is displayed to the user, if
* any.
*/
PropertyDescriptor& SetGroup(gd::String group_) {
group = group_;
return *this;
}
PropertyDescriptor& ClearChoices() {
choices.clear();
return *this;
}
PropertyDescriptor& AddChoice(const gd::String& value,
const gd::String& label) {
choices.push_back(PropertyDescriptorChoice(value, label));
return *this;
}
/**
* \brief Set and replace the additional information for the property.
*/
@@ -118,7 +150,8 @@ class GD_CORE_API PropertyDescriptor {
/**
* \brief Change the unit of measurement of the property value.
*/
PropertyDescriptor& SetMeasurementUnit(const gd::MeasurementUnit &measurementUnit_) {
PropertyDescriptor& SetMeasurementUnit(
const gd::MeasurementUnit& measurementUnit_) {
measurementUnit = measurementUnit_;
return *this;
}
@@ -128,14 +161,18 @@ class GD_CORE_API PropertyDescriptor {
const gd::String& GetLabel() const { return label; }
const gd::String& GetDescription() const { return description; }
const gd::String& GetGroup() const { return group; }
const gd::MeasurementUnit& GetMeasurementUnit() const { return measurementUnit; }
const gd::MeasurementUnit& GetMeasurementUnit() const {
return measurementUnit;
}
const std::vector<gd::String>& GetExtraInfo() const {
return extraInformation;
}
std::vector<gd::String>& GetExtraInfo() {
return extraInformation;
std::vector<gd::String>& GetExtraInfo() { return extraInformation; }
const std::vector<PropertyDescriptorChoice>& GetChoices() const {
return choices;
}
/**
@@ -178,23 +215,26 @@ class GD_CORE_API PropertyDescriptor {
bool IsAdvanced() const { return advanced; }
/**
* \brief Check if the property has impact on other properties - which means a change
* must re-render other properties.
* \brief Check if the property has impact on other properties - which means a
* change must re-render other properties.
*/
bool HasImpactOnOtherProperties() const { return hasImpactOnOtherProperties; }
/**
* \brief Set if the property has impact on other properties - which means a change
* must re-render other properties.
* \brief Set if the property has impact on other properties - which means a
* change must re-render other properties.
*/
PropertyDescriptor& SetHasImpactOnOtherProperties(bool enable) {
hasImpactOnOtherProperties = enable;
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const { return quickCustomizationVisibility; }
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
PropertyDescriptor& SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
PropertyDescriptor& SetQuickCustomizationVisibility(
QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
return *this;
}
@@ -231,15 +271,17 @@ class GD_CORE_API PropertyDescriptor {
gd::String label; //< The user-friendly property name
gd::String description; //< The user-friendly property description
gd::String group; //< The user-friendly property group
std::vector<PropertyDescriptorChoice>
choices; //< The optional choices for the property.
std::vector<gd::String>
extraInformation; ///< Can be used to store for example the available
///< choices, if a property is a displayed as a combo
///< box.
extraInformation; ///< Can be used to store an additional information
///< like an object type.
bool hidden;
bool deprecated;
bool advanced;
bool hasImpactOnOtherProperties;
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
gd::MeasurementUnit
measurementUnit; //< The unit of measurement of the property vale.
QuickCustomization::Visibility quickCustomizationVisibility;
};

View File

@@ -764,129 +764,6 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in event-based functions") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto &function = extension.GetEventsFunctions().InsertNewEventsFunction(
"MyFreeFunction", 0);
gd::StandardEvent standardEvent;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomethingWithResources");
instruction.SetParametersCount(3);
instruction.SetParameter(0, "res3");
instruction.SetParameter(1, "res1");
instruction.SetParameter(2, "res4");
standardEvent.GetActions().Insert(instruction);
function.GetEvents().InsertEvent(standardEvent);
auto& layout = project.InsertNewLayout("MyScene", 0);
// MyEventExtension::MyFreeFunction doesn't need to be actually used in
// events because the implementation is naive.
gd::ResourceExposer::ExposeLayoutResources(project, layout, worker);
REQUIRE(worker.bitmapFonts.size() == 1);
REQUIRE(worker.bitmapFonts[0] == "res3");
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
REQUIRE(worker.audios.size() == 1);
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in event-based behavior functions") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& behavior = extension.GetEventsBasedBehaviors().InsertNew("MyBehavior", 0);
auto& function = behavior.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0);
gd::StandardEvent standardEvent;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomethingWithResources");
instruction.SetParametersCount(3);
instruction.SetParameter(0, "res3");
instruction.SetParameter(1, "res1");
instruction.SetParameter(2, "res4");
standardEvent.GetActions().Insert(instruction);
function.GetEvents().InsertEvent(standardEvent);
auto& layout = project.InsertNewLayout("MyScene", 0);
// MyEventExtension::MyBehavior::MyFunction doesn't need to be actually used in
// events because the implementation is naive.
gd::ResourceExposer::ExposeLayoutResources(project, layout, worker);
REQUIRE(worker.bitmapFonts.size() == 1);
REQUIRE(worker.bitmapFonts[0] == "res3");
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
REQUIRE(worker.audios.size() == 1);
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in event-based object functions") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& object = extension.GetEventsBasedObjects().InsertNew("MyObject", 0);
auto& function = object.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0);
gd::StandardEvent standardEvent;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomethingWithResources");
instruction.SetParametersCount(3);
instruction.SetParameter(0, "res3");
instruction.SetParameter(1, "res1");
instruction.SetParameter(2, "res4");
standardEvent.GetActions().Insert(instruction);
function.GetEvents().InsertEvent(standardEvent);
auto& layout = project.InsertNewLayout("MyScene", 0);
// MyEventExtension::MyObject::MyFunction doesn't need to be actually used in
// events because the implementation is naive.
gd::ResourceExposer::ExposeLayoutResources(project, layout, worker);
REQUIRE(worker.bitmapFonts.size() == 1);
REQUIRE(worker.bitmapFonts[0] == "res3");
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
REQUIRE(worker.audios.size() == 1);
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in layer effects") {
gd::Project project;
gd::Platform platform;

View File

@@ -0,0 +1,522 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering events of GDevelop Core.
*/
#include "catch.hpp"
#include <algorithm>
#include <initializer_list>
#include <map>
#include "GDCore/CommonTools.h"
#include "GDCore/IDE/EventsBasedObjectVariantHelper.h"
#include "DummyPlatform.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
#include "catch.hpp"
gd::InitialInstance *
GetFirstInstanceOf(const gd::String objectName,
gd::InitialInstancesContainer &initialInstances) {
gd::InitialInstance *variantInstance = nullptr;
initialInstances.IterateOverInstances(
[&variantInstance, &objectName](gd::InitialInstance &instance) {
if (instance.GetObjectName() == objectName) {
variantInstance = &instance;
return true;
}
return false;
});
return variantInstance;
}
gd::EventsBasedObject &SetupEventsBasedObject(gd::Project &project) {
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
auto &object = eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject", 0);
object.GetVariables().InsertNew("MyVariable").SetValue(123);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &instance =
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
instance.SetObjectName("MyChildObject");
instance.GetVariables().InsertNew("MyVariable").SetValue(111);
auto &objectGroup =
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew(
"MyObjectGroup");
objectGroup.AddObject("MyChildObject");
return eventsBasedObject;
}
TEST_CASE("EventsBasedObjectVariantHelper", "[common]") {
SECTION("Can add missing objects") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject2"));
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject3"));
}
SECTION("Can remove objects") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
variant.GetInitialInstances().InsertNewInitialInstance().SetObjectName(
"MyChildObject2");
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject(
"MyChildObject2") == true);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().RemoveObject("MyChildObject2");
eventsBasedObject.GetObjects().RemoveObject("MyChildObject3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject2") == false);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject3") == false);
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject(
"MyChildObject2") == false);
}
SECTION("Can change object type") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().RemoveObject("MyChildObject");
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::FakeObjectWithDefaultBehavior", "MyChildObject",
0);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects().GetObject("MyChildObject").GetType() ==
"MyExtension::FakeObjectWithDefaultBehavior");
REQUIRE(variant.GetInitialInstances().GetInstancesCount() == 1);
}
SECTION("Can add missing object groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew("MyObjectGroup2",
0);
eventsBasedObject.GetObjects()
.GetObjectGroups()
.InsertNew("MyObjectGroup3", 0)
.AddObject("MyChildObject");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(variantObjectGroups.Has("MyObjectGroup2"));
REQUIRE(variantObjectGroups.Has("MyObjectGroup3"));
REQUIRE(
variantObjectGroups.Get("MyObjectGroup").GetAllObjectsNames().size() ==
1);
REQUIRE(
variantObjectGroups.Get("MyObjectGroup2").GetAllObjectsNames().size() ==
0);
REQUIRE(
variantObjectGroups.Get("MyObjectGroup3").GetAllObjectsNames().size() ==
1);
}
SECTION("Can remove object groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew("MyObjectGroup2",
0);
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew("MyObjectGroup3",
0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
eventsBasedObject.GetObjects().GetObjectGroups().Remove("MyObjectGroup2");
eventsBasedObject.GetObjects().GetObjectGroups().Remove("MyObjectGroup3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(variantObjectGroups.Has("MyObjectGroup2") == false);
REQUIRE(variantObjectGroups.Has("MyObjectGroup3") == false);
}
SECTION("Can add objects to groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &objectGroup =
eventsBasedObject.GetObjects().GetObjectGroups().Get("MyObjectGroup");
objectGroup.AddObject("MyChildObject2");
objectGroup.AddObject("MyChildObject3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(
variantObjectGroups.Get("MyObjectGroup").GetAllObjectsNames().size() ==
3);
}
SECTION("Can remove objects from groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
auto &objectGroup =
eventsBasedObject.GetObjects().GetObjectGroups().Get("MyObjectGroup");
objectGroup.AddObject("MyChildObject2");
objectGroup.AddObject("MyChildObject3");
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
objectGroup.RemoveObject("MyChildObject2");
objectGroup.RemoveObject("MyChildObject3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(
variantObjectGroups.Get("MyObjectGroup").GetAllObjectsNames().size() ==
1);
}
SECTION("Can add missing behaviors") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior2");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior"));
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior2"));
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior3"));
}
SECTION("Can remove missing behaviors") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior2");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior3");
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
object.RemoveBehavior("MyBehavior2");
object.RemoveBehavior("MyBehavior3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior"));
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior2") == false);
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior3") == false);
}
SECTION("Can change behavior type") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.RemoveBehavior("MyBehavior");
object.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyBehavior");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior"));
REQUIRE(variantObject.GetBehavior("MyBehavior").GetTypeName() ==
"MyExtension::MyOtherBehavior");
}
SECTION("Can add missing variables") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.GetVariables().InsertNew("MyVariable2", 1).SetValue(456);
object.GetVariables().InsertNew("MyVariable3", 2).SetValue(789);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetValue() == 123);
REQUIRE(variantObject.GetVariables().Get("MyVariable2").GetValue() == 456);
REQUIRE(variantObject.GetVariables().Get("MyVariable3").GetValue() == 789);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Has("MyVariable"));
REQUIRE(objectInstance->GetVariables().Has("MyVariable2") == false);
REQUIRE(objectInstance->GetVariables().Has("MyVariable3") == false);
}
}
SECTION("Can keep variable value") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
// Do the changes and launch the refactoring.
object.GetVariables().Get("MyVariable").SetValue(456);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetValue() == 123);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Get("MyVariable").GetValue() ==
111);
}
}
SECTION("Must not propagate instance variable value changes") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
{
auto *objectInstance = GetFirstInstanceOf(
"MyChildObject", eventsBasedObject.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
objectInstance->GetVariables().Get("MyVariable").SetValue(222);
}
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Get("MyVariable").GetValue() ==
111);
}
}
SECTION("Can move variables") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.GetVariables().InsertNew("MyVariable2", 1).SetValue(456);
object.GetVariables().InsertNew("MyVariable3", 2).SetValue(789);
{
auto *objectInstance = GetFirstInstanceOf(
"MyChildObject", eventsBasedObject.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
objectInstance->GetVariables().Get("MyVariable2").SetValue(222);
objectInstance->GetVariables().Get("MyVariable3").SetValue(333);
}
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
object.GetVariables().Move(2, 0);
object.GetVariables().Get("MyVariable").SetValue(111);
object.GetVariables().Get("MyVariable2").SetValue(222);
object.GetVariables().Get("MyVariable3").SetValue(333);
REQUIRE(object.GetVariables().GetNameAt(0) == "MyVariable3");
REQUIRE(object.GetVariables().GetNameAt(1) == "MyVariable");
REQUIRE(object.GetVariables().GetNameAt(2) == "MyVariable2");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetValue() == 123);
REQUIRE(variantObject.GetVariables().Get("MyVariable2").GetValue() == 456);
REQUIRE(variantObject.GetVariables().Get("MyVariable3").GetValue() == 789);
REQUIRE(variantObject.GetVariables().GetNameAt(0) == "MyVariable3");
REQUIRE(variantObject.GetVariables().GetNameAt(1) == "MyVariable");
REQUIRE(variantObject.GetVariables().GetNameAt(2) == "MyVariable2");
}
SECTION("Can remove variables") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.GetVariables().InsertNew("MyVariable2", 1).SetValue(456);
object.GetVariables().InsertNew("MyVariable3", 2).SetValue(789);
{
auto *objectInstance = GetFirstInstanceOf(
"MyChildObject", eventsBasedObject.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
objectInstance->GetVariables().Get("MyVariable2").SetValue(222);
objectInstance->GetVariables().Get("MyVariable3").SetValue(333);
}
// Do the changes and launch the refactoring.
object.GetVariables().Remove("MyVariable2");
object.GetVariables().Remove("MyVariable3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Has("MyVariable"));
REQUIRE(variantObject.GetVariables().Has("MyVariable2") == false);
REQUIRE(variantObject.GetVariables().Has("MyVariable3") == false);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Has("MyVariable"));
REQUIRE(objectInstance->GetVariables().Has("MyVariable2") == false);
REQUIRE(objectInstance->GetVariables().Has("MyVariable3") == false);
}
}
SECTION("Can change variable type") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
// Do the changes and launch the refactoring.
object.GetVariables().Get("MyVariable").SetString("abc");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetString() ==
"abc");
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject("MyVariable") ==
false);
}
}

View File

@@ -33,7 +33,132 @@ using namespace gd;
TEST_CASE("ObjectAssetSerializer", "[common]") {
SECTION("Can serialize custom objects as assets") {
SECTION("Can serialize custom objects as assets with variant") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
eventsBasedObject.SetFullName("My events based object");
eventsBasedObject.SetDescription("An events based object for test");
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChild", 0);
auto &childInstance =
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
childInstance.SetObjectName("MyChild");
auto &resourceManager = project.GetResourcesManager();
gd::ImageResource imageResource;
imageResource.SetName("assets/Idle.png");
imageResource.SetFile("assets/Idle.png");
imageResource.SetSmooth(true);
resourceManager.AddResource(imageResource);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyEventsExtension::MyEventsBasedObject", "MyObject", 0);
auto *spriteConfiguration =
dynamic_cast<gd::SpriteObject *>(&childObject.GetConfiguration());
REQUIRE(spriteConfiguration != nullptr);
{
gd::Animation animation;
animation.SetName("Idle");
animation.SetDirectionsCount(1);
auto &direction = animation.GetDirection(0);
gd::Sprite frame;
frame.SetImageName("assets/Idle.png");
direction.AddSprite(frame);
spriteConfiguration->GetAnimations().AddAnimation(animation);
}
SerializerElement assetElement;
std::vector<gd::String> usedResourceNames;
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
assetElement, usedResourceNames);
// This list is used to copy resource files.
REQUIRE(usedResourceNames.size() == 1);
REQUIRE(usedResourceNames[0] == "assets/Idle.png");
// Check that the project is left untouched.
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
REQUIRE(resourceManager.GetResource("assets/Idle.png").GetFile() ==
"assets/Idle.png");
REQUIRE(!resourceManager.HasResource("Idle.png"));
REQUIRE(assetElement.HasChild("objectAssets"));
auto &objectAssetsElement = assetElement.GetChild("objectAssets");
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
REQUIRE(objectAssetsElement.GetChildrenCount() == 1);
auto &objectAssetElement = objectAssetsElement.GetChild(0);
REQUIRE(objectAssetElement.HasChild("variants"));
auto &variantsElement = objectAssetElement.GetChild("variants");
variantsElement.ConsiderAsArrayOf("variant");
REQUIRE(variantsElement.GetChildrenCount() == 1);
auto &variantPairElement = variantsElement.GetChild(0);
REQUIRE(variantPairElement.GetStringAttribute("objectType") ==
"MyEventsExtension::MyEventsBasedObject");
REQUIRE(variantPairElement.HasChild("variant"));
auto &variantElement = variantPairElement.GetChild("variant");
REQUIRE(variantElement.GetStringAttribute("name") == "");
REQUIRE(variantElement.HasChild("objects"));
auto &objectsElement = variantElement.GetChild("objects");
objectsElement.ConsiderAsArrayOf("object");
REQUIRE(objectsElement.GetChildrenCount() == 1);
auto &childElement = objectsElement.GetChild(0);
REQUIRE(childElement.HasChild("animations"));
auto &animationsElement = childElement.GetChild("animations");
animationsElement.ConsiderAsArrayOf("animation");
REQUIRE(animationsElement.GetChildrenCount() == 1);
auto &animationElement = animationsElement.GetChild(0);
REQUIRE(animationElement.GetStringAttribute("name") == "Idle");
auto &directionsElement = animationElement.GetChild("directions");
directionsElement.ConsiderAsArrayOf("direction");
REQUIRE(directionsElement.GetChildrenCount() == 1);
auto &directionElement = directionsElement.GetChild(0);
auto &spritesElement = directionElement.GetChild("sprites");
spritesElement.ConsiderAsArrayOf("sprite");
REQUIRE(spritesElement.GetChildrenCount() == 1);
auto &spriteElement = spritesElement.GetChild(0);
REQUIRE(spriteElement.GetStringAttribute("image") == "assets/Idle.png");
REQUIRE(objectAssetElement.HasChild("requiredExtensions"));
auto &requiredExtensionsElement =
objectAssetElement.GetChild("requiredExtensions");
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
REQUIRE(requiredExtensionsElement.GetChildrenCount() == 1);
auto &requiredExtensionElement = requiredExtensionsElement.GetChild(0);
REQUIRE(requiredExtensionElement.GetStringAttribute("extensionName") ==
"MyEventsExtension");
// Resources are renamed according to asset script naming conventions.
REQUIRE(objectAssetElement.HasChild("resources"));
auto &resourcesElement = objectAssetElement.GetChild("resources");
resourcesElement.ConsiderAsArrayOf("resource");
REQUIRE(resourcesElement.GetChildrenCount() == 1);
{
auto &resourceElement = resourcesElement.GetChild(0);
REQUIRE(resourceElement.GetStringAttribute("name") == "assets/Idle.png");
REQUIRE(resourceElement.GetStringAttribute("file") == "assets/Idle.png");
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
}
// Resources used in object configuration are updated.
REQUIRE(objectAssetElement.HasChild("object"));
auto &objectElement = objectAssetElement.GetChild("object");
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
REQUIRE(objectElement.GetStringAttribute("type") ==
"MyEventsExtension::MyEventsBasedObject");
}
SECTION("Can serialize custom objects as assets with children overriding") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
@@ -59,6 +184,8 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
auto &configuration = object.GetConfiguration();
auto *customObjectConfiguration =
dynamic_cast<gd::CustomObjectConfiguration *>(&configuration);
customObjectConfiguration
->SetMarkedAsOverridingEventsBasedObjectChildrenConfiguration(true);
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(
&customObjectConfiguration->GetChildObjectConfiguration("MyChild"));
REQUIRE(spriteConfiguration != nullptr);

View File

@@ -150,7 +150,7 @@ TEST_CASE("ObjectContainersList (GetTypeOfObject)", "[common]") {
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "FakeObjectWithDefaultBehavior", "MyObject2", 0);
project, "MyExtension::FakeObjectWithDefaultBehavior", "MyObject2", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());

View File

@@ -139,8 +139,8 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
.SetLabel("Dot shape")
.SetDescription("The shape is used for collision.")
.SetGroup("Movement");
property.GetExtraInfo().push_back("Dot shape");
property.GetExtraInfo().push_back("Bounding disk");
property.AddChoice("DotShape", "Dot shape");
property.AddChoice("BoundingDisk", "Bounding disk");
gd::PropertyFunctionGenerator::GenerateBehaviorGetterAndSetter(
project, extension, behavior, property, false);
@@ -157,7 +157,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
gd::EventsFunction::ExpressionAndCondition);
REQUIRE(getter.GetExpressionType().GetName() == "stringWithSelector");
REQUIRE(getter.GetExpressionType().GetExtraInfo() ==
"[\"Dot shape\",\"Bounding disk\"]");
"[\"DotShape\",\"BoundingDisk\"]");
}
SECTION("Can generate functions for a boolean property in a behavior") {
@@ -386,8 +386,8 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
.SetLabel("Dot shape")
.SetDescription("The shape is used for collision.")
.SetGroup("Movement");
property.GetExtraInfo().push_back("Dot shape");
property.GetExtraInfo().push_back("Bounding disk");
property.AddChoice("DotShape", "Dot shape");
property.AddChoice("BoundingDisk", "Bounding disk");
gd::PropertyFunctionGenerator::GenerateObjectGetterAndSetter(
project, extension, object, property);
@@ -404,7 +404,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
gd::EventsFunction::ExpressionAndCondition);
REQUIRE(getter.GetExpressionType().GetName() == "stringWithSelector");
REQUIRE(getter.GetExpressionType().GetExtraInfo() ==
"[\"Dot shape\",\"Bounding disk\"]");
"[\"DotShape\",\"BoundingDisk\"]");
}
SECTION("Can generate functions for a boolean property in an object") {

View File

@@ -1072,6 +1072,99 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
REQUIRE(instance.GetVariables().Get("MyRenamedVariable").GetValue() == 456);
}
SECTION("Can rename an object variable (in events-based object)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
auto &object = eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject", 0);
object.GetVariables().InsertNew("MyVariable").SetValue(123);
auto &instance =
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
instance.SetObjectName("MyChildObject");
instance.GetVariables().InsertNew("MyVariable").SetValue(456);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
gd::InitialInstance *variantInstance = nullptr;
variant.GetInitialInstances().IterateOverInstances(
[&variantInstance](gd::InitialInstance &instance) {
variantInstance = &instance;
return true;
});
REQUIRE(variantInstance != nullptr);
variant.GetObjects()
.GetObject("MyChildObject")
.GetVariables()
.Get("MyVariable")
.SetValue(111);
variantInstance->GetVariables().Get("MyVariable").SetValue(222);
auto &objectFunction =
eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
"MyObjectEventsFunction", 0);
gd::StandardEvent &event = dynamic_cast<gd::StandardEvent &>(
objectFunction.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
{
gd::Instruction action;
action.SetType("SetNumberObjectVariable");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MyChildObject"));
action.SetParameter(1, gd::Expression("MyVariable"));
action.SetParameter(2, gd::Expression("="));
action.SetParameter(3, gd::Expression("MyChildObject.MyVariable"));
event.GetActions().Insert(action);
}
// Do the changes and launch the refactoring.
object.GetVariables().ResetPersistentUuid();
gd::SerializerElement originalSerializedVariables;
object.GetVariables().SerializeTo(originalSerializedVariables);
object.GetVariables().Rename("MyVariable", "MyRenamedVariable");
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
originalSerializedVariables, object.GetVariables());
REQUIRE(changeset.oldToNewVariableNames.size() == 1);
gd::WholeProjectRefactorer::ApplyRefactoringForObjectVariablesContainer(
project, object.GetVariables(), eventsBasedObject.GetInitialInstances(),
object.GetName(), changeset, originalSerializedVariables);
gd::ObjectVariableHelper::ApplyChangesToVariants(
eventsBasedObject, "MyChildObject", changeset);
REQUIRE(event.GetActions()[0].GetParameter(1).GetPlainString() ==
"MyRenamedVariable");
REQUIRE(event.GetActions()[0].GetParameter(3).GetPlainString() ==
"MyChildObject.MyRenamedVariable");
REQUIRE(eventsBasedObject.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(eventsBasedObject.GetObjects()
.GetObject("MyChildObject")
.GetVariables()
.Get("MyRenamedVariable")
.GetValue() == 123);
REQUIRE(instance.GetVariables().Get("MyRenamedVariable").GetValue() == 456);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects()
.GetObject("MyChildObject")
.GetVariables()
.Get("MyRenamedVariable")
.GetValue() == 111);
REQUIRE(
variantInstance->GetVariables().Get("MyRenamedVariable").GetValue() ==
222);
}
SECTION("Can delete an object variable") {
gd::Project project;
gd::Platform platform;

View File

@@ -1754,6 +1754,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "Object2", 0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Create the objects container for the events function
gd::ObjectsContainer parametersObjectsContainer(
gd::ObjectsContainer::SourceType::Function);
@@ -1765,6 +1768,10 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject(
project, projectScopedContainers, eventsBasedObject, "Object1",
"Object3", /* isObjectGroup =*/false);
REQUIRE(variant.GetObjects().HasObjectNamed("Object1") == false);
REQUIRE(variant.GetObjects().HasObjectNamed("Object2") == true);
REQUIRE(variant.GetObjects().HasObjectNamed("Object3") == true);
REQUIRE(eventsBasedObject.GetObjects().GetObjectGroups().size() == 1);
REQUIRE(eventsBasedObject.GetObjects().GetObjectGroups()[0].Find(
"Object1") == false);
@@ -1772,6 +1779,17 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"Object2") == true);
REQUIRE(eventsBasedObject.GetObjects().GetObjectGroups()[0].Find(
"Object3") == true);
REQUIRE(variant.GetObjects().HasObjectNamed("Object1") == false);
REQUIRE(variant.GetObjects().HasObjectNamed("Object2") == true);
REQUIRE(variant.GetObjects().HasObjectNamed("Object3") == true);
REQUIRE(variant.GetObjects().GetObjectGroups().size() == 1);
REQUIRE(variant.GetObjects().GetObjectGroups()[0].Find("Object1") ==
false);
REQUIRE(variant.GetObjects().GetObjectGroups()[0].Find("Object2") ==
true);
REQUIRE(variant.GetObjects().GetObjectGroups()[0].Find("Object3") ==
true);
}
SECTION("Initial instances") {
@@ -1796,6 +1814,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsBasedObject.GetInitialInstances().InsertInitialInstance(instance1);
eventsBasedObject.GetInitialInstances().InsertInitialInstance(instance2);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Create the objects container for the events function
gd::ObjectsContainer parametersObjectsContainer(
gd::ObjectsContainer::SourceType::Function);
@@ -1807,10 +1828,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject(
project, projectScopedContainers, eventsBasedObject, "Object1",
"Object3", /* isObjectGroup =*/false);
REQUIRE(eventsBasedObject.GetInitialInstances().HasInstancesOfObject(
"Object1") == false);
REQUIRE(eventsBasedObject.GetInitialInstances().HasInstancesOfObject(
"Object3") == true);
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject("Object1") ==
false);
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject("Object3") ==
true);
}
SECTION("Events") {

View File

@@ -5,8 +5,6 @@ namespace gdjs {
type Object3DNetworkSyncDataType = {
// z is position on the Z axis, different from zo, which is Z order
z: number;
w: number;
h: number;
d: number;
rx: number;
ry: number;
@@ -112,12 +110,12 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Object3DNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Object3DNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
z: this.getZ(),
w: this.getWidth(),
h: this.getHeight(),
d: this.getDepth(),
rx: this.getRotationX(),
ry: this.getRotationY(),
@@ -127,11 +125,12 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: Object3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.w !== undefined) this.setWidth(networkSyncData.w);
if (networkSyncData.h !== undefined) this.setHeight(networkSyncData.h);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
this.setRotationX(networkSyncData.rx);

View File

@@ -25,6 +25,8 @@ namespace gdjs {
topFaceVisible: boolean;
bottomFaceVisible: boolean;
tint: string | undefined;
isCastingShadow: boolean;
isReceivingShadow: boolean;
materialType: 'Basic' | 'StandardWithoutMetalness';
};
}
@@ -71,8 +73,10 @@ namespace gdjs {
string,
];
_materialType: gdjs.Cube3DRuntimeObject.MaterialType =
gdjs.Cube3DRuntimeObject.MaterialType.Basic;
gdjs.Cube3DRuntimeObject.MaterialType.StandardWithoutMetalness;
_tint: string;
_isCastingShadow: boolean = true;
_isReceivingShadow: boolean = true;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -121,6 +125,8 @@ namespace gdjs {
];
this._tint = objectData.content.tint || '255;255;255';
this._isCastingShadow = objectData.content.isCastingShadow || false;
this._isReceivingShadow = objectData.content.isReceivingShadow || false;
this._materialType = this._convertMaterialType(
objectData.content.materialType
@@ -430,13 +436,27 @@ namespace gdjs {
) {
this.setMaterialType(newObjectData.content.materialType);
}
if (
oldObjectData.content.isCastingShadow !==
newObjectData.content.isCastingShadow
) {
this.updateShadowCasting(newObjectData.content.isCastingShadow);
}
if (
oldObjectData.content.isReceivingShadow !==
newObjectData.content.isReceivingShadow
) {
this.updateShadowReceiving(newObjectData.content.isReceivingShadow);
}
return true;
}
getNetworkSyncData(): Cube3DObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Cube3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
fo: this._facesOrientation,
bfu: this._backFaceUpThroughWhichAxisRotation,
@@ -448,9 +468,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: Cube3DObjectNetworkSyncData
networkSyncData: Cube3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;
@@ -531,6 +552,14 @@ namespace gdjs {
this._materialType = newMaterialType;
this._renderer._updateMaterials();
}
updateShadowCasting(value: boolean) {
this._isCastingShadow = value;
this._renderer.updateShadowCasting();
}
updateShadowReceiving(value: boolean) {
this._isReceivingShadow = value;
this._renderer.updateShadowReceiving();
}
}
export namespace Cube3DRuntimeObject {

View File

@@ -81,13 +81,14 @@ namespace gdjs {
.map((_, index) =>
getFaceMaterial(runtimeObject, materialIndexToFaceIndex[index])
);
const boxMesh = new THREE.Mesh(geometry, materials);
super(runtimeObject, instanceContainer, boxMesh);
this._boxMesh = boxMesh;
this._cube3DRuntimeObject = runtimeObject;
boxMesh.receiveShadow = this._cube3DRuntimeObject._isReceivingShadow;
boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
this.updateSize();
this.updatePosition();
this.updateRotation();
@@ -114,6 +115,13 @@ namespace gdjs {
new THREE.BufferAttribute(new Float32Array(tints), 3)
);
}
updateShadowCasting() {
this._boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
}
updateShadowReceiving() {
this._boxMesh.receiveShadow =
this._cube3DRuntimeObject._isReceivingShadow;
}
updateFace(faceIndex: integer) {
const materialIndex = faceIndexToMaterialIndex[faceIndex];

View File

@@ -1,4 +1,16 @@
namespace gdjs {
type CustomObject3DNetworkSyncDataType = {
z: float;
d: float;
rx: float;
ry: float;
ifz: boolean;
ccz: float;
};
type CustomObject3DNetworkSyncData = CustomObjectNetworkSyncData &
CustomObject3DNetworkSyncDataType;
/**
* Base class for 3D custom objects.
*/
@@ -34,7 +46,6 @@ namespace gdjs {
objectData: gdjs.Object3DData & gdjs.CustomObjectConfiguration
) {
super(parent, objectData);
this._renderer.reinitialize(this, parent);
}
protected override _createRender() {
@@ -78,6 +89,36 @@ namespace gdjs {
}
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): CustomObject3DNetworkSyncData {
return {
...super.getNetworkSyncData(syncOptions),
z: this.getZ(),
d: this.getDepth(),
rx: this.getRotationX(),
ry: this.getRotationY(),
ifz: this.isFlippedZ(),
ccz: this._customCenterZ,
};
}
updateFromNetworkSyncData(
networkSyncData: CustomObject3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
this.setRotationX(networkSyncData.rx);
if (networkSyncData.ry !== undefined)
this.setRotationY(networkSyncData.ry);
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
if (networkSyncData.ccz !== undefined)
this._customCenterZ = networkSyncData.ccz;
}
/**
* Set the object position on the Z axis.
*/

View File

@@ -44,10 +44,7 @@ namespace gdjs {
) {
this._object = object;
this._isContainerDirty = true;
const layer = parent.getLayer('');
if (layer) {
layer.getRenderer().add3DRendererObject(this._threeGroup);
}
this._threeGroup.clear();
}
_updateThreeGroup() {

View File

@@ -6,6 +6,7 @@ namespace gdjs {
r: number;
t: string;
}
const shadowHelper = false;
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::DirectionalLight',
new (class implements gdjs.PixiFiltersTools.FilterCreator {
@@ -17,19 +18,63 @@ namespace gdjs {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
light: THREE.DirectionalLight;
rotationObject: THREE.Group;
_isEnabled: boolean = false;
top: string = 'Y-';
elevation: float = 45;
rotation: float = 0;
private _top: string = 'Z+';
private _elevation: float = 45;
private _rotation: float = 0;
private _shadowMapSize: float = 1024;
private _minimumShadowBias: float = 0;
private _distanceFromCamera: float = 1500;
private _frustumSize: float = 4000;
private _isEnabled: boolean = false;
private _light: THREE.DirectionalLight;
private _shadowMapDirty = true;
private _shadowCameraDirty = true;
private _shadowCameraHelper: THREE.CameraHelper | null;
constructor() {
this.light = new THREE.DirectionalLight();
this.light.position.set(1, 0, 0);
this.rotationObject = new THREE.Group();
this.rotationObject.add(this.light);
this.updateRotation();
this._light = new THREE.DirectionalLight();
if (shadowHelper) {
this._shadowCameraHelper = new THREE.CameraHelper(
this._light.shadow.camera
);
} else {
this._shadowCameraHelper = null;
}
this._light.shadow.camera.updateProjectionMatrix();
}
private _updateShadowCamera(): void {
if (!this._shadowCameraDirty) {
return;
}
this._shadowCameraDirty = false;
this._light.shadow.camera.near = 1;
this._light.shadow.camera.far = this._distanceFromCamera + 10000;
this._light.shadow.camera.right = this._frustumSize / 2;
this._light.shadow.camera.left = -this._frustumSize / 2;
this._light.shadow.camera.top = this._frustumSize / 2;
this._light.shadow.camera.bottom = -this._frustumSize / 2;
}
private _updateShadowMapSize(): void {
if (!this._shadowMapDirty) {
return;
}
this._shadowMapDirty = false;
this._light.shadow.mapSize.set(
this._shadowMapSize,
this._shadowMapSize
);
// Force the recreation of the shadow map texture:
this._light.shadow.map?.dispose();
this._light.shadow.map = null;
this._light.shadow.needsUpdate = true;
}
isEnabled(target: EffectsTarget): boolean {
@@ -53,7 +98,12 @@ namespace gdjs {
if (!scene) {
return false;
}
scene.add(this.rotationObject);
scene.add(this._light);
scene.add(this._light.target);
if (this._shadowCameraHelper) {
scene.add(this._shadowCameraHelper);
}
this._isEnabled = true;
return true;
}
@@ -65,82 +115,164 @@ namespace gdjs {
if (!scene) {
return false;
}
scene.remove(this.rotationObject);
scene.remove(this._light);
scene.remove(this._light.target);
if (this._shadowCameraHelper) {
scene.remove(this._shadowCameraHelper);
}
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updatePreRender(target: gdjs.EffectsTarget): any {
// Apply any update to the camera or shadow map size.
this._updateShadowCamera();
this._updateShadowMapSize();
// Avoid shadow acne due to depth buffer precision.
const biasMultiplier =
this._shadowMapSize < 1024
? 2
: this._shadowMapSize < 2048
? 1.25
: 1;
this._light.shadow.bias = -this._minimumShadowBias * biasMultiplier;
// Apply update to the light position and its target.
// By doing this, the shadows are "following" the GDevelop camera.
if (!target.getRuntimeLayer) {
return;
}
const layer = target.getRuntimeLayer();
const x = layer.getCameraX();
const y = layer.getCameraY();
const z = layer.getCameraZ(layer.getInitialCamera3DFieldOfView());
const roundedX = Math.floor(x / 100) * 100;
const roundedY = Math.floor(y / 100) * 100;
const roundedZ = Math.floor(z / 100) * 100;
if (this._top === 'Y-') {
const posLightX =
roundedX +
this._distanceFromCamera *
Math.cos(gdjs.toRad(-this._rotation + 90)) *
Math.cos(gdjs.toRad(this._elevation));
const posLightY =
roundedY -
this._distanceFromCamera *
Math.sin(gdjs.toRad(this._elevation));
const posLightZ =
roundedZ +
this._distanceFromCamera *
Math.sin(gdjs.toRad(-this._rotation + 90)) *
Math.cos(gdjs.toRad(this._elevation));
this._light.position.set(posLightX, posLightY, posLightZ);
this._light.target.position.set(roundedX, roundedY, roundedZ);
} else {
const posLightX =
roundedX +
this._distanceFromCamera *
Math.cos(gdjs.toRad(this._rotation)) *
Math.cos(gdjs.toRad(this._elevation));
const posLightY =
roundedY +
this._distanceFromCamera *
Math.sin(gdjs.toRad(this._rotation)) *
Math.cos(gdjs.toRad(this._elevation));
const posLightZ =
roundedZ +
this._distanceFromCamera *
Math.sin(gdjs.toRad(this._elevation));
this._light.position.set(posLightX, posLightY, posLightZ);
this._light.target.position.set(roundedX, roundedY, roundedZ);
}
}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'intensity') {
this.light.intensity = value;
this._light.intensity = value;
} else if (parameterName === 'elevation') {
this.elevation = value;
this.updateRotation();
this._elevation = value;
} else if (parameterName === 'rotation') {
this.rotation = value;
this.updateRotation();
this._rotation = value;
} else if (parameterName === 'distanceFromCamera') {
this._distanceFromCamera = value;
} else if (parameterName === 'frustumSize') {
this._frustumSize = value;
} else if (parameterName === 'minimumShadowBias') {
this._minimumShadowBias = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'intensity') {
return this.light.intensity;
return this._light.intensity;
} else if (parameterName === 'elevation') {
return this.elevation;
return this._elevation;
} else if (parameterName === 'rotation') {
return this.rotation;
return this._rotation;
} else if (parameterName === 'distanceFromCamera') {
return this._distanceFromCamera;
} else if (parameterName === 'frustumSize') {
return this._frustumSize;
} else if (parameterName === 'minimumShadowBias') {
return this._minimumShadowBias;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'color') {
this.light.color = new THREE.Color(
this._light.color = new THREE.Color(
gdjs.rgbOrHexStringToNumber(value)
);
}
if (parameterName === 'top') {
this.top = value;
this.updateRotation();
this._top = value;
}
if (parameterName === 'shadowQuality') {
if (value === 'low' && this._shadowMapSize !== 512) {
this._shadowMapSize = 512;
this._shadowMapDirty = true;
}
if (value === 'medium' && this._shadowMapSize !== 1024) {
this._shadowMapSize = 1024;
this._shadowMapDirty = true;
}
if (value === 'high' && this._shadowMapSize !== 2048) {
this._shadowMapSize = 2048;
this._shadowMapDirty = true;
}
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this.light.color.setHex(value);
this._light.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this.light.color.getHex();
return this._light.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
updateRotation() {
if (this.top === 'Z+') {
// 0° is a light from the right of the screen.
this.rotationObject.rotation.z = gdjs.toRad(this.rotation);
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
} else {
// 0° becomes a light from Z+.
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
updateBooleanParameter(parameterName: string, value: boolean): void {
if (parameterName === 'isCastingShadow') {
this._light.castShadow = value;
}
}
getNetworkSyncData(): DirectionalLightFilterNetworkSyncData {
return {
i: this.light.intensity,
c: this.light.color.getHex(),
e: this.elevation,
r: this.rotation,
t: this.top,
i: this._light.intensity,
c: this._light.color.getHex(),
e: this._elevation,
r: this._rotation,
t: this._top,
};
}
updateFromNetworkSyncData(syncData: any): void {
this.light.intensity = syncData.i;
this.light.color.setHex(syncData.c);
this.elevation = syncData.e;
this.rotation = syncData.r;
this.top = syncData.t;
this.updateRotation();
this._light.intensity = syncData.i;
this._light.color.setHex(syncData.c);
this._elevation = syncData.e;
this._rotation = syncData.r;
this._top = syncData.t;
}
})();
}

View File

@@ -18,18 +18,15 @@ namespace gdjs {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
light: THREE.HemisphereLight;
rotationObject: THREE.Group;
_top: string = 'Z+';
_elevation: float = 90;
_rotation: float = 0;
_isEnabled: boolean = false;
top: string = 'Y-';
elevation: float = 45;
rotation: float = 0;
_light: THREE.HemisphereLight;
constructor() {
this.light = new THREE.HemisphereLight();
this.light.position.set(1, 0, 0);
this.rotationObject = new THREE.Group();
this.rotationObject.add(this.light);
this._light = new THREE.HemisphereLight();
this.updateRotation();
}
@@ -54,7 +51,7 @@ namespace gdjs {
if (!scene) {
return false;
}
scene.add(this.rotationObject);
scene.add(this._light);
this._isEnabled = true;
return true;
}
@@ -66,96 +63,106 @@ namespace gdjs {
if (!scene) {
return false;
}
scene.remove(this.rotationObject);
scene.remove(this._light);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'intensity') {
this.light.intensity = value;
this._light.intensity = value;
} else if (parameterName === 'elevation') {
this.elevation = value;
this._elevation = value;
this.updateRotation();
} else if (parameterName === 'rotation') {
this.rotation = value;
this._rotation = value;
this.updateRotation();
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'intensity') {
return this.light.intensity;
return this._light.intensity;
} else if (parameterName === 'elevation') {
return this.elevation;
return this._elevation;
} else if (parameterName === 'rotation') {
return this.rotation;
return this._rotation;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'skyColor') {
this.light.color = new THREE.Color(
this._light.color = new THREE.Color(
gdjs.rgbOrHexStringToNumber(value)
);
}
if (parameterName === 'groundColor') {
this.light.groundColor = new THREE.Color(
this._light.groundColor = new THREE.Color(
gdjs.rgbOrHexStringToNumber(value)
);
}
if (parameterName === 'top') {
this.top = value;
this._top = value;
this.updateRotation();
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'skyColor') {
this.light.color.setHex(value);
this._light.color.setHex(value);
}
if (parameterName === 'groundColor') {
this.light.groundColor.setHex(value);
this._light.groundColor.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'skyColor') {
return this.light.color.getHex();
return this._light.color.getHex();
}
if (parameterName === 'groundColor') {
return this.light.groundColor.getHex();
return this._light.groundColor.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
updateRotation() {
if (this.top === 'Z+') {
// 0° is a light from the right of the screen.
this.rotationObject.rotation.z = gdjs.toRad(this.rotation);
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
if (this._top === 'Y-') {
// `rotation` at 0° becomes a light from Z+.
this._light.position.set(
Math.cos(gdjs.toRad(-this._rotation + 90)) *
Math.cos(gdjs.toRad(this._elevation)),
-Math.sin(gdjs.toRad(this._elevation)),
Math.sin(gdjs.toRad(-this._rotation + 90)) *
Math.cos(gdjs.toRad(this._elevation))
);
} else {
// 0° becomes a light from Z+.
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
// `rotation` at 0° is a light from the right of the screen.
this._light.position.set(
Math.cos(gdjs.toRad(this._rotation)) *
Math.cos(gdjs.toRad(this._elevation)),
Math.sin(gdjs.toRad(this._rotation)) *
Math.cos(gdjs.toRad(this._elevation)),
Math.sin(gdjs.toRad(this._elevation))
);
}
}
getNetworkSyncData(): HemisphereLightFilterNetworkSyncData {
return {
i: this.light.intensity,
sc: this.light.color.getHex(),
gc: this.light.groundColor.getHex(),
e: this.elevation,
r: this.rotation,
t: this.top,
i: this._light.intensity,
sc: this._light.color.getHex(),
gc: this._light.groundColor.getHex(),
e: this._elevation,
r: this._rotation,
t: this._top,
};
}
updateFromNetworkSyncData(
syncData: HemisphereLightFilterNetworkSyncData
): void {
this.light.intensity = syncData.i;
this.light.color.setHex(syncData.sc);
this.light.groundColor.setHex(syncData.gc);
this.elevation = syncData.e;
this.rotation = syncData.r;
this.top = syncData.t;
this._light.intensity = syncData.i;
this._light.color.setHex(syncData.sc);
this._light.groundColor.setHex(syncData.gc);
this._elevation = syncData.e;
this._rotation = syncData.r;
this._top = syncData.t;
this.updateRotation();
}
})();

View File

@@ -21,7 +21,9 @@ module.exports = {
.setExtensionInformation(
'Scene3D',
_('3D'),
_('Support for 3D in GDevelop.'),
_(
'Support for 3D in GDevelop: this provides 3D objects and the common features for all 3D objects.'
),
'Florian Rival',
'MIT'
)
@@ -36,7 +38,9 @@ module.exports = {
'Base3DBehavior',
_('3D capability'),
'Object3D',
_('Move the object in 3D space.'),
_(
'Common features for all 3D objects: position in 3D space (including the Z axis, in addition to X and Y), size (including depth, in addition to width and height), rotation (on X and Y axis, in addition to the Z axis), scale (including Z axis, in addition to X and Y), flipping (on Z axis, in addition to horizontal (Y)/vertical (X) flipping).'
),
'',
'res/conditions/3d_box.svg',
'Base3DBehavior',
@@ -158,7 +162,12 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationX')
.setGetter('getRotationX');
@@ -174,7 +183,12 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationY')
.setGetter('getRotationY');
@@ -192,7 +206,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundX');
@@ -210,7 +224,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundY');
@@ -228,7 +242,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundZ');
}
@@ -238,7 +252,7 @@ module.exports = {
.addObject(
'Model3DObject',
_('3D Model'),
_('An animated 3D model.'),
_('An animated 3D model, useful for most elements of a 3D game.'),
'JsPlatform/Extensions/3d_model.svg',
new gd.Model3DObjectConfiguration()
)
@@ -590,7 +604,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setHidden()
.setFunctionName('setRotationX')
.setGetter('getRotationX');
@@ -607,7 +626,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setHidden()
.setFunctionName('setRotationY')
.setGetter('getRotationY');
@@ -626,7 +650,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundX');
@@ -645,7 +669,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundY');
@@ -664,7 +688,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundZ');
@@ -855,7 +879,9 @@ module.exports = {
propertyName === 'rightFaceResourceRepeat' ||
propertyName === 'topFaceResourceRepeat' ||
propertyName === 'bottomFaceResourceRepeat' ||
propertyName === 'enableTextureTransparency'
propertyName === 'enableTextureTransparency' ||
propertyName === 'isCastingShadow' ||
propertyName === 'isReceivingShadow'
) {
objectContent[propertyName] = newValue === '1';
return true;
@@ -883,8 +909,8 @@ module.exports = {
.getOrCreate('facesOrientation')
.setValue(objectContent.facesOrientation || 'Y')
.setType('choice')
.addExtraInfo('Y')
.addExtraInfo('Z')
.addChoice('Y', 'Y')
.addChoice('Z', 'Z')
.setLabel(_('Faces orientation'))
.setDescription(
_(
@@ -944,8 +970,8 @@ module.exports = {
.getOrCreate('backFaceUpThroughWhichAxisRotation')
.setValue(objectContent.backFaceUpThroughWhichAxisRotation || 'X')
.setType('choice')
.addExtraInfo('X')
.addExtraInfo('Y')
.addChoice('X', 'X')
.addChoice('Y', 'Y')
.setLabel(_('Back face orientation'))
.setDescription(
_(
@@ -1079,11 +1105,29 @@ module.exports = {
objectProperties
.getOrCreate('materialType')
.setValue(objectContent.materialType || 'Basic')
.setValue(objectContent.materialType || 'StandardWithoutMetalness')
.setType('choice')
.addExtraInfo('Basic')
.addExtraInfo('StandardWithoutMetalness')
.setLabel(_('Material type'));
.addChoice('Basic', _('Basic (no lighting, no shadows)'))
.addChoice(
'StandardWithoutMetalness',
_('Standard (without metalness)')
)
.setLabel(_('Material type'))
.setGroup(_('Lighting'));
objectProperties
.getOrCreate('isCastingShadow')
.setValue(objectContent.isCastingShadow ? 'true' : 'false')
.setType('boolean')
.setLabel(_('Shadow casting'))
.setGroup(_('Lighting'));
objectProperties
.getOrCreate('isReceivingShadow')
.setValue(objectContent.isReceivingShadow ? 'true' : 'false')
.setType('boolean')
.setLabel(_('Shadow receiving'))
.setGroup(_('Lighting'));
return objectProperties;
};
@@ -1101,7 +1145,7 @@ module.exports = {
topFaceResourceName: '',
bottomFaceResourceName: '',
frontFaceVisible: true,
backFaceVisible: false,
backFaceVisible: true,
leftFaceVisible: true,
rightFaceVisible: true,
topFaceVisible: true,
@@ -1112,8 +1156,10 @@ module.exports = {
rightFaceResourceRepeat: false,
topFaceResourceRepeat: false,
bottomFaceResourceRepeat: false,
materialType: 'Basic',
materialType: 'StandardWithoutMetalness',
tint: '255;255;255',
isCastingShadow: true,
isReceivingShadow: true,
};
Cube3DObject.updateInitialInstanceProperty = function (
@@ -1467,7 +1513,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationX')
.setHidden()
.setGetter('getRotationX');
@@ -1484,7 +1535,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationY')
.setHidden()
.setGetter('getRotationY');
@@ -1852,7 +1908,9 @@ module.exports = {
.addEffect('AmbientLight')
.setFullName(_('Ambient light'))
.setDescription(
_('A light that illuminates all objects from every direction.')
_(
'A light that illuminates all objects from every direction. Often used along with a Directional light (though a Hemisphere light can be used instead of an Ambient light).'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
@@ -1873,7 +1931,11 @@ module.exports = {
const effect = extension
.addEffect('DirectionalLight')
.setFullName(_('Directional light'))
.setDescription(_('A very far light source like the sun.'))
.setDescription(
_(
"A very far light source like the sun. This is the light to use for casting shadows for 3D objects (other lights won't emit shadows). Often used along with a Hemisphere light."
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/DirectionalLight.js');
@@ -1890,11 +1952,11 @@ module.exports = {
.setType('number');
properties
.getOrCreate('top')
.setValue('Y-')
.setValue('Z+')
.setLabel(_('3D world top'))
.setType('choice')
.addExtraInfo('Y-')
.addExtraInfo('Z+')
.addExtraInfo('Y-')
.setGroup(_('Orientation'));
properties
.getOrCreate('elevation')
@@ -1909,6 +1971,47 @@ module.exports = {
.setLabel(_('Rotation (in degrees)'))
.setType('number')
.setGroup(_('Orientation'));
properties
.getOrCreate('isCastingShadow')
.setValue('false')
.setLabel(_('Shadow casting'))
.setType('boolean')
.setGroup(_('Shadows'));
properties
.getOrCreate('shadowQuality')
.setValue('medium')
.addChoice('low', _('Low quality'))
.addChoice('medium', _('Medium quality'))
.addChoice('high', _('High quality'))
.setLabel(_('Shadow quality'))
.setType('choice')
.setGroup(_('Shadows'));
properties
.getOrCreate('minimumShadowBias')
.setValue('0')
.setLabel(_('Shadow bias'))
.setDescription(
_(
'Use this to avoid "shadow acne" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 1.25 for medium quality and 2 for low quality.'
)
)
.setType('number')
.setGroup(_('Shadows'))
.setAdvanced(true);
properties
.getOrCreate('frustumSize')
.setValue('4000')
.setLabel(_('Shadow frustum size'))
.setType('number')
.setGroup(_('Shadows'))
.setAdvanced(true);
properties
.getOrCreate('distanceFromCamera')
.setValue('1500')
.setLabel(_("Distance from layer's camera"))
.setType('number')
.setGroup(_('Shadows'))
.setAdvanced(true);
}
{
const effect = extension
@@ -1916,7 +2019,7 @@ module.exports = {
.setFullName(_('Hemisphere light'))
.setDescription(
_(
'A light that illuminates objects from every direction with a gradient.'
'A light that illuminates objects from every direction with a gradient. Often used along with a Directional light.'
)
)
.markAsNotWorkingForObjects()
@@ -1940,11 +2043,11 @@ module.exports = {
.setType('number');
properties
.getOrCreate('top')
.setValue('Y-')
.setValue('Z+')
.setLabel(_('3D world top'))
.setType('choice')
.addExtraInfo('Y-')
.addExtraInfo('Z+')
.addExtraInfo('Y-')
.setGroup(_('Orientation'));
properties
.getOrCreate('elevation')
@@ -1960,6 +2063,48 @@ module.exports = {
.setType('number')
.setGroup(_('Orientation'));
}
{
const effect = extension
.addEffect('Skybox')
.setFullName(_('Skybox'))
.setDescription(
_('Display a background on a cube surrounding the scene.')
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/Skybox.js');
const properties = effect.getProperties();
properties
.getOrCreate('rightFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Right face (X+)'));
properties
.getOrCreate('leftFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Left face (X-)'));
properties
.getOrCreate('bottomFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Bottom face (Y+)'));
properties
.getOrCreate('topFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Top face (Y-)'));
properties
.getOrCreate('frontFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Front face (Z+)'));
properties
.getOrCreate('backFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Back face (Z-)'));
}
{
const effect = extension
.addEffect('HueAndSaturation')
@@ -3206,6 +3351,8 @@ module.exports = {
this._threeObject = new THREE.Group();
this._threeObject.rotation.order = 'ZYX';
this._threeObject.castShadow = true;
this._threeObject.receiveShadow = true;
this._threeGroup.add(this._threeObject);
}

View File

@@ -23,7 +23,7 @@ Model3DObjectConfiguration::Model3DObjectConfiguration()
: width(100), height(100), depth(100), rotationX(0), rotationY(0),
rotationZ(0), modelResourceName(""), materialType("StandardWithoutMetalness"),
originLocation("ModelOrigin"), centerLocation("ModelOrigin"),
keepAspectRatio(true), crossfadeDuration(0.1f) {}
keepAspectRatio(true), crossfadeDuration(0.1f), isCastingShadow(true), isReceivingShadow(true) {}
bool Model3DObjectConfiguration::UpdateProperty(const gd::String &propertyName,
const gd::String &newValue) {
@@ -75,6 +75,16 @@ bool Model3DObjectConfiguration::UpdateProperty(const gd::String &propertyName,
crossfadeDuration = newValue.To<double>();
return true;
}
if(propertyName == "isCastingShadow")
{
isCastingShadow = newValue == "1";
return true;
}
if(propertyName == "isReceivingShadow")
{
isReceivingShadow = newValue == "1";
return true;
}
return false;
}
@@ -143,19 +153,20 @@ Model3DObjectConfiguration::GetProperties() const {
objectProperties["materialType"]
.SetValue(materialType.empty() ? "Basic" : materialType)
.SetType("choice")
.AddExtraInfo("Basic")
.AddExtraInfo("StandardWithoutMetalness")
.AddExtraInfo("KeepOriginal")
.SetLabel(_("Material"));
.AddChoice("Basic", _("Basic (no lighting, no shadows)"))
.AddChoice("StandardWithoutMetalness", _("Standard (without metalness)"))
.AddChoice("KeepOriginal", _("Keep original"))
.SetLabel(_("Material"))
.SetGroup(_("Lighting"));
objectProperties["originLocation"]
.SetValue(originLocation.empty() ? "TopLeft" : originLocation)
.SetType("choice")
.AddExtraInfo("ModelOrigin")
.AddExtraInfo("TopLeft")
.AddExtraInfo("ObjectCenter")
.AddExtraInfo("BottomCenterZ")
.AddExtraInfo("BottomCenterY")
.AddChoice("ModelOrigin", _("Model origin"))
.AddChoice("TopLeft", _("Top left"))
.AddChoice("ObjectCenter", _("Object center"))
.AddChoice("BottomCenterZ", _("Bottom center (Z)"))
.AddChoice("BottomCenterY", _("Bottom center (Y)"))
.SetLabel(_("Origin point"))
.SetGroup(_("Points"))
.SetAdvanced(true);
@@ -163,10 +174,10 @@ Model3DObjectConfiguration::GetProperties() const {
objectProperties["centerLocation"]
.SetValue(centerLocation.empty() ? "ObjectCenter" : centerLocation)
.SetType("choice")
.AddExtraInfo("ModelOrigin")
.AddExtraInfo("ObjectCenter")
.AddExtraInfo("BottomCenterZ")
.AddExtraInfo("BottomCenterY")
.AddChoice("ModelOrigin", _("Model origin"))
.AddChoice("ObjectCenter", _("Object center"))
.AddChoice("BottomCenterZ", _("Bottom center (Z)"))
.AddChoice("BottomCenterY", _("Bottom center (Y)"))
.SetLabel(_("Center point"))
.SetGroup(_("Points"))
.SetAdvanced(true);
@@ -178,6 +189,20 @@ Model3DObjectConfiguration::GetProperties() const {
.SetGroup(_("Animations"))
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond());
objectProperties["isCastingShadow"]
.SetValue(isCastingShadow ? "true" : "false")
.SetType("boolean")
.SetLabel(_("Shadow casting"))
.SetGroup(_("Lighting"));
objectProperties["isReceivingShadow"]
.SetValue(isReceivingShadow ? "true" : "false")
.SetType("boolean")
.SetLabel(_("Shadow receiving"))
.SetGroup(_("Lighting"));
return objectProperties;
}
@@ -210,6 +235,8 @@ void Model3DObjectConfiguration::DoUnserializeFrom(
centerLocation = content.GetStringAttribute("centerLocation");
keepAspectRatio = content.GetBoolAttribute("keepAspectRatio");
crossfadeDuration = content.GetDoubleAttribute("crossfadeDuration");
isCastingShadow = content.GetBoolAttribute("isCastingShadow");
isReceivingShadow = content.GetBoolAttribute("isReceivingShadow");
RemoveAllAnimations();
auto &animationsElement = content.GetChild("animations");
@@ -239,6 +266,8 @@ void Model3DObjectConfiguration::DoSerializeTo(
content.SetAttribute("centerLocation", centerLocation);
content.SetAttribute("keepAspectRatio", keepAspectRatio);
content.SetAttribute("crossfadeDuration", crossfadeDuration);
content.SetAttribute("isCastingShadow", isCastingShadow);
content.SetAttribute("isReceivingShadow", isReceivingShadow);
auto &animationsElement = content.AddChild("animations");
animationsElement.ConsiderAsArrayOf("animation");

View File

@@ -160,6 +160,8 @@ public:
const gd::String& GetCenterLocation() const { return centerLocation; };
bool shouldKeepAspectRatio() const { return keepAspectRatio; };
bool shouldCastShadow() const { return isCastingShadow; };
bool shouldReceiveShadow() const { return isReceivingShadow; };
///@}
protected:
@@ -182,6 +184,8 @@ private:
gd::String centerLocation;
bool keepAspectRatio;
bool isCastingShadow;
bool isReceivingShadow;
std::vector<Model3DAnimation> animations;
static Model3DAnimation badAnimation; //< Bad animation when an out of bound

View File

@@ -8,6 +8,7 @@ namespace gdjs {
anis: Model3DAnimation[];
ai: integer;
ass: float;
aet: float;
ap: boolean;
cfd: float;
};
@@ -38,6 +39,8 @@ namespace gdjs {
| 'BottomCenterY';
animations: Model3DAnimation[];
crossfadeDuration: float;
isCastingShadow: boolean;
isReceivingShadow: boolean;
};
}
@@ -101,6 +104,8 @@ namespace gdjs {
_animationSpeedScale: float = 1;
_animationPaused: boolean = false;
_crossfadeDuration: float = 0;
_isCastingShadow: boolean = true;
_isReceivingShadow: boolean = true;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -123,6 +128,8 @@ namespace gdjs {
objectData.content.materialType
);
this.setIsCastingShadow(objectData.content.isCastingShadow);
this.setIsReceivingShadow(objectData.content.isReceivingShadow);
this.onModelChanged(objectData);
this._crossfadeDuration = objectData.content.crossfadeDuration || 0;
@@ -195,27 +202,43 @@ namespace gdjs {
newObjectData.content.centerLocation
);
}
if (
oldObjectData.content.isCastingShadow !==
newObjectData.content.isCastingShadow
) {
this.setIsCastingShadow(newObjectData.content.isCastingShadow);
}
if (
oldObjectData.content.isReceivingShadow !==
newObjectData.content.isReceivingShadow
) {
this.setIsReceivingShadow(newObjectData.content.isReceivingShadow);
}
return true;
}
getNetworkSyncData(): Model3DObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Model3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
op: this._originPoint,
cp: this._centerPoint,
anis: this._animations,
ai: this._currentAnimationIndex,
ass: this._animationSpeedScale,
aet: this.getAnimationElapsedTime(),
ap: this._animationPaused,
cfd: this._crossfadeDuration,
};
}
updateFromNetworkSyncData(
networkSyncData: Model3DObjectNetworkSyncData
networkSyncData: Model3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;
@@ -229,11 +252,14 @@ namespace gdjs {
if (networkSyncData.anis !== undefined) {
this._animations = networkSyncData.anis;
}
if (networkSyncData.ass !== undefined) {
this.setAnimationSpeedScale(networkSyncData.ass);
}
if (networkSyncData.ai !== undefined) {
this.setAnimationIndex(networkSyncData.ai);
}
if (networkSyncData.ass !== undefined) {
this.setAnimationSpeedScale(networkSyncData.ass);
if (networkSyncData.aet !== undefined) {
this.setAnimationElapsedTime(networkSyncData.aet);
}
if (networkSyncData.ap !== undefined) {
if (networkSyncData.ap !== this.isAnimationPaused()) {
@@ -358,6 +384,16 @@ namespace gdjs {
return this._renderer.hasAnimationEnded();
}
setIsCastingShadow(value: boolean): void {
this._isCastingShadow = value;
this._renderer._updateShadow();
}
setIsReceivingShadow(value: boolean): void {
this._isReceivingShadow = value;
this._renderer._updateShadow();
}
setCrossfadeDuration(duration: number): void {
if (this._crossfadeDuration === duration) return;
this._crossfadeDuration = duration;

View File

@@ -286,6 +286,7 @@ namespace gdjs {
this.get3DRendererObject().remove(this._threeObject);
this.get3DRendererObject().add(threeObject);
this._threeObject = threeObject;
this._updateShadow();
// Start the current animation on the new 3D object.
this._animationMixer = new THREE.AnimationMixer(root);
@@ -323,6 +324,13 @@ namespace gdjs {
return this._originalModel.animations[animationIndex].name;
}
_updateShadow() {
this._threeObject.traverse((child) => {
child.castShadow = this._model3DRuntimeObject._isCastingShadow;
child.receiveShadow = this._model3DRuntimeObject._isReceivingShadow;
});
}
/**
* Return true if animation has ended.
* The animation had ended if:

102
Extensions/3D/Skybox.ts Normal file
View File

@@ -0,0 +1,102 @@
namespace gdjs {
interface SkyboxFilterNetworkSyncData {}
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Skybox',
new (class implements gdjs.PixiFiltersTools.FilterCreator {
makeFilter(
target: EffectsTarget,
effectData: EffectData
): gdjs.PixiFiltersTools.Filter {
if (typeof THREE === 'undefined') {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
_cubeTexture: THREE.CubeTexture;
_oldBackground:
| THREE.CubeTexture
| THREE.Texture
| THREE.Color
| null = null;
_isEnabled: boolean = false;
constructor() {
this._cubeTexture = target
.getRuntimeScene()
.getGame()
.getImageManager()
.getThreeCubeTexture(
effectData.stringParameters.rightFaceResourceName,
effectData.stringParameters.leftFaceResourceName,
effectData.stringParameters.topFaceResourceName,
effectData.stringParameters.bottomFaceResourceName,
effectData.stringParameters.frontFaceResourceName,
effectData.stringParameters.backFaceResourceName
);
}
isEnabled(target: EffectsTarget): boolean {
return this._isEnabled;
}
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
if (this._isEnabled === enabled) {
return true;
}
if (enabled) {
return this.applyEffect(target);
} else {
return this.removeEffect(target);
}
}
applyEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
// TODO Add a background stack in LayerPixiRenderer to allow
// filters to stack them.
this._oldBackground = scene.background;
scene.background = this._cubeTexture;
if (!scene.environment) {
scene.environment = this._cubeTexture;
}
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
scene.background = this._oldBackground;
scene.environment = null;
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {}
getDoubleParameter(parameterName: string): number {
return 0;
}
updateStringParameter(parameterName: string, value: string): void {}
updateColorParameter(parameterName: string, value: number): void {}
getColorParameter(parameterName: string): number {
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
getNetworkSyncData(): SkyboxFilterNetworkSyncData {
return {};
}
updateFromNetworkSyncData(
syncData: SkyboxFilterNetworkSyncData
): void {}
})();
}
})()
);
}

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