Compare commits

...

192 Commits

Author SHA1 Message Date
Florian Rival
3d4766e5eb Fix issues with npm install and npm version 2022-01-11 11:42:26 +00:00
D8H
a1f0bbf25f Add autocompletion for fields and expressions asking for a function parameter name (#3470) 2022-01-10 21:42:20 +00:00
AlexandreS
8af84bf3a4 Only run auto closes and comments on issue opening (#3468)
Do not show in changelog
2022-01-10 18:48:03 +01:00
Florian Rival
1ebe50a30c Fix npm install on Travis (#3455) 2022-01-10 17:47:03 +00:00
AlexandreS
370e20fc97 Attempt to solve special characters use issue 3 - Remove escaping first set to escape quotes (#3465)
Do not show in changelog
2022-01-10 18:39:12 +01:00
AlexandreS
fb407848be Attempt to solve special characters use issue 3 - Use issue number instead of its id (#3462)
Do not show in changelog
2022-01-10 18:23:30 +01:00
AlexandreS
d2f53edb2c Attempt to solve special characters use issue 3
Do not show in changelog
2022-01-10 18:14:12 +01:00
AlexandreS
34a75a29c7 Improve a few things Bis repetita
Do not show in changelog
2022-01-10 18:06:18 +01:00
AlexandreS
3acd76ff02 Close ExtensionInstallDialog if successfully installed
Do not show in changelog
2022-01-10 16:33:37 +01:00
AlexandreS
0c6ffc23ed Few export fixes (#3452)
Do not show in changelog
2022-01-10 14:30:18 +01:00
Clément Pasteau
323809228a Update Games Platform local URL to use Liluo
Do not show in changelog
2022-01-10 14:08:28 +01:00
github-actions[bot]
145764bfdb Update translations [skip ci] (#3364)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2022-01-09 17:06:17 +00:00
Leo_Red
dba4b7aeb7 Update new behavior method dialog step icon to be hollow (#3441) 2022-01-09 12:59:34 +00:00
AlexandreS
1f19be3ec2 Fixes presence of ghost collision masks for objects with platform behavior
Co-authored-by: D8H <Davy.Helard@gmail.com>
2022-01-08 11:55:33 +00:00
Florian Rival
bf60470c3d Make the GDevelop Windows version signed with a certificate to avoid warnings when opening the installer (#3435)
* The Windows warning should be gone once a sufficient number of people install the app
2022-01-07 23:57:23 +00:00
Florian Rival
2afa702080 Bump newIDE version (testing)
Don't show in changelog
2022-01-07 17:13:33 +00:00
AlexandreS
365bc56940 Change the way one can apply filters when searching for extensions/behaviors/examples in the stores
Co-authored-by: Bouh <bouhvivez@gmail.com>
2022-01-07 15:28:36 +00:00
Clément Pasteau
8aaa3bcbb6 Introduce stable URLs for games
* A unique URL is now available for each game, on liluo.io
* The list of builds has been improved to allow opening web builds and decide which one to publish live
* Web export is now split in 2 steps, providing an instant-build URL, and the stable game URL
2022-01-06 16:45:08 +01:00
Clément Pasteau
90c3195b5e Improve the mouse conditions to autocomplete possible values (#3433) 2022-01-06 10:26:48 +01:00
Aurélien Vivet
ad3c7e4fad Fix typo (#3434)
Do not show in changelog
2022-01-06 10:05:13 +01:00
AlexandreS
ba50c73485 Add possibility to add an extension or a behavior when adding an instruction in the event sheet 2022-01-05 15:36:03 +01:00
AlexandreS
933287ec6b Add notion of origin to extensions (#3429)
Do not show in changelog.
2022-01-04 15:14:52 +01:00
Florian Rival
e2afa946a2 Fix the SerializedExtension type, which is not a type containing the same fields as the extension "headers" coming from the extensions store.
Don't show in changelog
2022-01-04 12:35:20 +01:00
D8H
ae6a77da9f Improve calculations for the Platformer to make it more precise (#3417)
* This makes the movement less prone to approximations when there is a drop in FPS, making your game more precise.
2022-01-03 16:19:17 +01:00
Clément Pasteau
25453b70eb Fix Extension Install dialog being functional to use hooks
Do not show in changelog
2022-01-03 09:51:25 +01:00
D8H
cff585ed55 Improve calculations for the Pathfinding to make it more precise (#3421)
* This makes the movement less prone to approximations when there is a drop in FPS, making your game more precise.
2022-01-02 19:28:03 +01:00
Arthur Pacaud
79a4162ad0 Add logs to audio actions when trying to manipulate a sound/music on a non-existing channel (#3241)
Only show in developer changelog
2022-01-02 14:59:42 +01:00
Florian Rival
9e21cf0a08 Add options and logos to build properly the AppX (#3015)
* Fix wrong executable name because of the space in "GDevelop 5.exe" in the generated appx.
* Use Node.js 16 on Linux CI
* Update version [skip ci]
* Upgrade to electron-builder 22.11.11

Only show in developer changelog
2021-12-30 10:22:43 +01:00
AlexandreS
63332f8123 Add possibility to use Cmd+Enter/Ctrl+Enter to submit a dialog 2021-12-29 11:27:03 +01:00
Florian Rival
ce986fe1d3 Add a test game for shape painter transformations
Don't show in changelog
2021-12-28 10:11:46 +01:00
D8H
439d185ce8 Add a condition to the Platformer character behavior to check if a control is pressed (or simulated) (#3406) 2021-12-28 10:09:04 +01:00
AlexandreS
ec42219d2f Remove tendentious adjective from project name generator
Do not show in changelog
2021-12-28 10:06:47 +01:00
D8H
fa5671a3ee Improve calculations for the Top Down movement behavior to make it more precise when accelerating or decelerating (#3412)
* This makes the movement less prone to approximations when there is a drop in FPS, making your game more precise.
2021-12-28 09:57:10 +01:00
Clément Pasteau
399c4c5edd Make Builds list consistent across the app
Do not show in changelog
2021-12-27 19:37:26 +01:00
Clément Pasteau
3cc3f612e6 Display the list of builds per game in the game dashboard 2021-12-27 14:42:03 +01:00
Clément Pasteau
3e1799dddb Send gameId when creating a build
Do not show in changelog
2021-12-27 09:14:00 +01:00
D8H
eb6628af49 Add 2 tests for Platformer jump at 30 fps and 120 fps. (#3408)
Only show in developer changelog
2021-12-27 09:03:04 +01:00
D8H
fc6082c35b Add support for rotating, scaling and flipping a Shape Painter object (#3402)
* A custom center point can be set (and will be at the center of the drawing if not specified).
* The collision mask is now a rectangle surrounding what is drawn on screen. 
* If this changes something in your game, or you want another collision mask, you can use the action to set a custom rectangle as the collision mask of the object.
2021-12-26 22:13:51 +00:00
D8H
819ffc52c7 Show objects position instead of origin in the in-game debugging view (#3403)
* Still show the "origin" point for rotated/scaled Sprite objects.
2021-12-25 18:51:57 +01:00
Arthur Pacaud
f36c9940ed Fix the special 'Not' condition not working with some conditions related to objects (#3367) 2021-12-24 13:36:58 +01:00
Florian Rival
cd8901a524 Fix typo 2021-12-24 10:56:22 +01:00
Aurélien Vivet
5693b257c0 Fix the expression to get the user home path (#3400) 2021-12-23 15:14:42 +01:00
Clément Pasteau
48467e4654 Add missing keys in the keyboard expression autocomplete 2021-12-23 11:09:10 +01:00
Rahul Saini
78dfedf66b Allow multi-selection and sorting in the instances list (#3343)
* Also allow to lock/unlock by clicking on the lock icon
2021-12-23 11:08:53 +01:00
AlexandreS
d3ef6fe729 Add context menu items to collapse all events and expand events to certain levels (#3396) 2021-12-23 09:49:08 +01:00
D8H
b3e0540fed Group properties in some behaviors for enhanced clarity (#3373)
* Also allow custom behaviors made in the editor to define property groups.
2021-12-22 17:10:06 +00:00
Clément Pasteau
510d8d7c1d Fix user profile being refreshed too often when building
Do not show in changelog
2021-12-22 10:13:47 +01:00
AlexandreS
650975ba6e Improve homepage layout and Create project dialog options (#3386) 2021-12-21 14:28:13 +01:00
Aurélien Vivet
6bf293bcb5 Add backdrop click on create new project dialog
Don't show in changelog
2021-12-21 04:15:52 +01:00
D8H
dac4b3ba51 Improve clarity of code (#3375)
Don't show in changelog
2021-12-17 16:38:37 +01:00
D8H
b344f5b956 Allow actions/conditions/expressions created in the editor to be sorted in groups (#3366) 2021-12-17 15:43:58 +01:00
AlexandreS
44db5362d3 Add notification badge on user chip and achievements 2021-12-17 10:59:11 +01:00
Clément Pasteau
cca0e6e66f Fix toolbar not centered on the extensions screen (#3374) 2021-12-17 10:37:11 +01:00
Arthur Pacaud
81c65f7ff7 Fix wrong network preview address shown when running in VirtualBox/VMware (#3368) 2021-12-16 10:20:58 +01:00
Clément Pasteau
b3ea46d7e6 Fix sharing buttons
Do not show in changelog
2021-12-15 13:57:25 +01:00
github-actions[bot]
0e8adaab92 Update translations 2021-12-15 13:01:35 +01:00
D8H
c055fbcb3c Split the Platformer tests in 5 files (#3360)
Only show in developer changelog
2021-12-15 11:57:43 +01:00
AlexandreS
5051de0787 Add Tutorial Opened event when clicking on tutorial on homepage
Do not show in changelog
2021-12-15 11:22:32 +01:00
Clément Pasteau
4976d8ef8b Bump IDE version
Do not show in changelog
2021-12-15 10:48:30 +01:00
Clément Pasteau
bf1ffd3e65 Improve layer visibility toggle with an explanation text 2021-12-15 10:44:52 +01:00
Clément Pasteau
9163e998f9 Improve web export with multiple sharing capabilities 2021-12-15 10:25:41 +01:00
AlexandreS
a4d0c591a8 Add possibility to choose project name before it is created
A random and fun name is generated as a suggestion
2021-12-14 17:51:26 +01:00
Florian Rival
8c717ba910 Fix videos wrongly removed removing unused resources in the resources editor
Fix #3356
2021-12-14 10:15:55 +01:00
Leo_Red
be0f760f02 Add minor UI improvements to the profile dialog (#3351) 2021-12-12 11:45:14 +00:00
Florian Rival
919d596d07 Move the copy button for the web export link to the end of the input (#3350)
Also use a component doing the layout of the button on the right.

Don't show in changelog
2021-12-11 13:06:39 +00:00
AlexandreS
35cfd627ad Clean up after new homepage
Do not show in changelog
2021-12-10 16:24:06 +01:00
D8H
ba687aa60c Allow grid based object to optimise collision checks (#3245)
* Allow to get the hit boxes for a given area.
* Also remove useless array and wrong sharing of vertices in Light object renderers

Only show in developer changelog
2021-12-10 15:16:12 +00:00
Clément Pasteau
4d8e835b9a Fix light textures sometimes not behaving properly when close to an obstacle 2021-12-10 14:45:02 +01:00
Clément Pasteau
834a28ddbc Improve the whole export flow
* The Preview & Publish buttons are now centred in the interface
* The automated web upload with a provided link is put forward in the Export home dialog
* The export flow has been reworked to be split into "Automated" and "Manual'
2021-12-10 12:36:48 +01:00
AlexandreS
945555a8e9 Fix switch case condition in showcased game buttons
Don't show in changelog
2021-12-10 10:32:05 +00:00
AlexandreS
ad3d1dd8c3 Change homepage to display starters, tutorials and the game showcase
Additionally:
- Add button with split menu to open recent projects
- Remove starters tab in the dialog to create a project
2021-12-09 17:46:52 +01:00
Florian Rival
fd47282456 Improve resilience of resources fetching of the desktop app (#3342)
Do more requests at the same time but retry the failing ones.

Don't show in changelog
2021-12-08 09:54:20 +00:00
Florian Rival
dff1c88ef7 Increase parallel requests when fetching resources in the desktop app
Don't show in changelog
2021-12-07 22:35:27 +00:00
Florian Rival
4ea622ff99 Fix typo 2021-12-05 13:04:40 +00:00
Florian Rival
17ea918a91 Bump newIDE version 2021-12-03 11:23:32 +00:00
Leo_Red
cc6af8979d Animate opening of lists in the Project Manager, action/condition selector and in the Debugger (#3310) 2021-12-03 11:16:56 +00:00
github-actions[bot]
132e20fd24 Update translations [skip ci] (#3289)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2021-12-03 11:11:31 +00:00
Florian Rival
fb6a88785a Add Pandako to the list of contributors (Japanese translations, extensions and blog)
Don't show in changelog
2021-12-03 09:43:02 +00:00
Clément Pasteau
8a159d7ff5 Remove unused var
Do not show in changelog
2021-12-03 09:30:50 +00:00
Florian Rival
13c85bbe45 Improve custom behavior new function dialog to show a button for making an action, condition or expression instead of a single one for action
Don't show in changelog
2021-12-02 22:20:08 +00:00
Florian Rival
ce8323e8e1 Fix crash when modifying the operator for an action or condition of a "color" property of a behavior (#3327) 2021-12-02 15:54:00 +00:00
Clément Pasteau
dbc7a74e45 Fix debugger actions not hiding properly after opening it 2021-12-02 15:53:25 +01:00
Clément Pasteau
cfb1d6888e Fix sounds sometimes not playing after the first time being played (#3325) 2021-12-02 10:48:10 +00:00
Florian Rival
816dc8cc74 Fix tweens automatically deleting the object sometimes affecting newly created objects (#3321) 2021-12-02 09:40:14 +00:00
Arthur Pacaud
106549e5fa Allow usage of custom ICE servers in the P2P extension (#3301) 2021-12-01 20:08:25 +00:00
Clément Pasteau
f8ca06d530 Fix web debugger icon not updating properly
Do not show in changelog
2021-12-01 19:32:18 +01:00
AlexandreS
34cbcdbc3a Add carousel component for new start page (don't show in changelog) 2021-12-01 15:11:56 +01:00
Clément Pasteau
3b208502ae Improve DismissableTutorialMessage story
Do not show in changelog
2021-11-30 15:50:28 +01:00
Clément Pasteau
e3654fca99 improve tutorials to be fetched from backend services
Do not show in changelog
2021-11-30 14:43:57 +01:00
Florian Rival
2a386cdcf1 Fix the layer of the created object not shown for the "Create object" action in the events sheet 2021-11-29 23:43:50 +00:00
Leo_Red
b134896687 Fix wrong mention of extensions instead of examples in a text (#3306) 2021-11-29 13:16:34 +00:00
Leo_Red
705dff43bc Move scene variables into their own category in actions/conditions/expressions (#3300) 2021-11-27 16:33:31 +00:00
Leo_Red
d9eaf71ed1 Fix tabs in preference dialog disappearing when scrolling in the dialog (#3299) 2021-11-27 16:06:20 +00:00
D8H
008b4291ab Fix the "separate" action when there are several obstacles (#3236)
* Previously an object colliding with multiple objects would have been "pushed" too far from the two colliding objects. Now, it gets properly separated, in a much more natural way, avoiding some shaking/flickering on corners or when involving multiple obstacles.
2021-11-25 17:45:38 +00:00
Clément Pasteau
3596896b16 Bump IDE version 2021-11-24 17:12:25 +01:00
Clément Pasteau
db05e98bc8 Improve object lock behavior in the editor
* The objects are now selectable, to simplify unlocking them
2021-11-24 17:12:01 +01:00
Florian Rival
98c1a93da5 Update a wording the instance properties editor
Don't show in changelog
2021-11-24 15:26:44 +00:00
Florian Rival
c39d3ee35c Fix a React warning
Don't show in changelog
2021-11-24 15:13:05 +00:00
Florian Rival
c68a25573d Hide direction related actions/conditions/expressions
The builtin "8 directions" is not officially supported anymore and we should re-enable it later with an improved interface/more flexible choices.

Don't shoe in changelog
2021-11-24 15:05:18 +00:00
github-actions[bot]
2b72b6b3e7 Update translations [skip ci] (#3285)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2021-11-24 14:24:53 +00:00
Clément Pasteau
db60151150 Fix Tiled Sprite being incorrectly displayed (pixelated) when the X/Y offset was too large (#3287) 2021-11-24 14:05:04 +00:00
Leo_Red
0971a4b464 Move the object variables editor into the objects editor (easier to find and faster to access) (#3263)
* Also rework the other variables editor dialogs to have the toolbar buttons always at the bottom of the window.
2021-11-24 13:21:38 +00:00
Clément Pasteau
93a57b1a31 Fix the display of the decreased build limits after exporting a game (#3284) 2021-11-24 09:40:27 +00:00
github-actions[bot]
d0f7e2517d Update translations [skip ci] (#3262)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2021-11-23 21:48:25 +00:00
AlexandreS
9523c98cad Add multiple achievements ("badges") that can be earned while using GDevelop (#3256)
* These achievements are visible in your user profile.
* They are all for now fairly simple to collect, but we'll also later add some a bit more complex to win, to reward contributors, people invested in the community and people building great games!
2021-11-23 21:29:27 +00:00
Florian Rival
ea38a2ff0f Add conditions and expressions to compare the coordinates of the bounding box of an object (#3275) 2021-11-23 14:56:49 +00:00
Clément Pasteau
3065ba53b1 Fix arrow keys wrongly triggering page scroll when playing a game embedded in a web page (#3280) 2021-11-23 10:57:09 +00:00
Clément Pasteau
dc19f030fc Improve scene editor scrolling speed to be faster/slower according to the zoom level (#3279) 2021-11-23 10:33:21 +00:00
Florian Rival
9fb36a375f Add minor UI changes
* Don't open asset categories in the asset store
* Remove a button redirecting to the asset store as there is already a tab for it
* Make the new object dialog always take the full height of the window
2021-11-21 18:03:09 +00:00
D8H
a366934fdb Avoid to fetch again a JSON in the game engine when it's already being fetched (#3261) 2021-11-21 12:30:59 +00:00
Florian Rival
9626ea6dcf Indicate on link events that they can't be used in an extension/custom behavior 2021-11-20 17:18:21 +00:00
Oxey405
08388893bf Don't list the current scene/external event name in a Link event when choosing what to include (#3228)
* This prevents an infinite loop/crash if launching a preview after selecting the current scene/external events in a link of the same scene/external events.
2021-11-20 15:20:45 +00:00
Arthur Pacaud
2f933f2cad Refactor the implementation of the Tween behavior (#3218)
Only show in developer changelog
2021-11-20 12:37:32 +00:00
Clément Pasteau
2517b47401 Feature/allow user receive game stats (#3259)
Add a checkbox to user profile to receive weekly game stats via email
2021-11-19 16:03:23 +01:00
D8H
86cad60194 Fix platforms sometimes not properly detected when rotated and at the edge of the scene (#3260) 2021-11-19 09:32:24 +00:00
github-actions[bot]
b1658d4619 Update translations [skip ci] (#3253)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2021-11-16 21:24:17 +01:00
Florian Rival
c72026e8cd Show the usernames of the contributors on the wiki pages for extensions 2021-11-16 16:07:05 +00:00
Florian Rival
4936b4b104 Fix compilation on MinGW
Don't show in changelog
2021-11-16 14:50:05 +00:00
Florian Rival
5623d12eac Fix TimeScale expression not shown in expressions autocompletion
* Also fix inconsistent naming of time scale related actions/conditions and expressions.

Fix #3248
2021-11-16 11:18:29 +00:00
Clément Pasteau
8757cfe8b2 Fix autocomplete not displaying options if only translatable values (#3254)
Do not show in changelog
2021-11-16 10:51:51 +00:00
MyNameIsRinax
968402e99f Fix a typo for rotate toward angle and position in the event sheet sentence (#3252) 2021-11-16 08:21:43 +01:00
github-actions[bot]
6f59a0921d Update translations [skip ci] (#3239)
Don't show in changelog
Co-authored-by: Bouh <Bouh@users.noreply.github.com>
2021-11-15 17:26:12 +01:00
Leo_Red
167307f1c4 Fix a typo for rotate action in the event sheet sentence (#3249) 2021-11-15 17:17:04 +01:00
Florian Rival
36fb4ec9b2 Bump newIDE version 2021-11-12 11:47:21 +00:00
Florian Rival
124e1f3683 Improve changelog extraction script
Don't show in changelog
2021-11-12 11:46:57 +00:00
github-actions[bot]
9c350729a8 Update translations [skip ci] (#3235) 2021-11-12 11:43:27 +00:00
Florian Rival
9186daa782 Update Platformer starter to allow the character to walk on rotated platforms
Don't show in changelog
2021-11-11 17:18:14 +00:00
Florian Rival
c6161c4752 Add an option in the Sprite editor to allow animations to play even when the object is hidden/outside the screen 2021-11-11 17:08:40 +00:00
D8H
5d3f207216 Add major improvements to the platformer engine to better handle slopes and moving platforms (#3009)
* The characters on platform are no long stopping when going from one rotated platform to another.
* Platforms going up and down are now properly handled by characters - they won't fall or vibrate like before. You can now freely use platforms doing any movement and characters will stay on them and can move freely on them.
* Characters Y position will now stay stable when moving on a flat platform and between jumps.
* Note that if you use the collision condition to check if an object is touching a platform, you should instead use the condition "Is object on given floor": this will always work consistently.
2021-11-11 16:11:02 +00:00
Florian Rival
cf462f6c6e Fix volume sounds and musics not clamped at exactly 0 and 1 (#3234)
Don't show in changelog
2021-11-10 11:36:20 +00:00
github-actions[bot]
bc979031e3 Update translations (#3202) 2021-11-09 16:08:14 +00:00
Clément Pasteau
406bae5e12 Fix a glitch where a sound being played at a low volume can actually be heard at a high volume for a split second 2021-11-09 16:00:56 +00:00
Florian Rival
5f5f50e039 Fix wording
Don't show in changelog
2021-11-09 14:43:53 +00:00
Florian Rival
394fb4c587 Fix warning
Don't show in changelog
2021-11-08 15:24:07 +00:00
Florian Rival
599d48afca Refactor scan/removal of unused resources
Don't show in changelog
2021-11-07 19:38:50 +00:00
Florian Rival
bccef185cb Clean up some code
Don't show in changelog
2021-11-07 19:38:50 +00:00
Florian Rival
d0f4370026 Add support for blob urls in the web-app export/preview
Experimental support - just so the behavior of the export/preview are correct.

Don't show in changelog
2021-11-07 19:38:50 +00:00
Florian Rival
77d6f0310c Add support for using arbitrary URLs for images/sounds/resources in the web-app 2021-11-07 19:38:50 +00:00
Florian Rival
c73a5a046f Fix semaphore CI npm cache not working because of npm 7+ upgrading the package-lock.json
Using "npm ci" to allow clean installation without changes to the lock files.

Don't show in changelog
2021-11-04 11:48:04 +00:00
Florian Rival
c37e129a5b Implement support for the Debugger in the web-app
* One or more preview windows can be launched and used with the GDevelop Debugger, like on the desktop app.
  * To run the Debugger, click on the button next to the Play button in the toolbar and choose "Start Preview with Debugger and Performance Profiler"
* This is useful to inspect instances of objects, inspect internal messages or run the performance profiler.
* A right click on the Play button will also allow to launch a new preview, in a new window.
* Also fix the loading screen not shown in the preview on the web-app even when asked to be shown (using the game properties preview button)
2021-11-04 11:48:04 +00:00
Florian Rival
aeecb0e29f Revert Rectangle to Line in the Particle Emitter editor
Don't show in changelog
2021-11-02 10:26:57 +00:00
Tristan Rhodes
a6525e5617 Clarify names of particle types in the Particle Emitter editor (#3217) 2021-11-01 14:35:10 +00:00
Arthur Pacaud
f67aeedaeb Update esbuild (#3220)
Only show in developer changelog
2021-11-01 10:01:52 +00:00
Leo_Red
0c2f023c63 Fix contributors list (#3221) 2021-11-01 09:55:03 +00:00
Leo_Red
d6d4569dbf Update naming of events in the menus to make them easier to understand (#3212) 2021-10-29 14:47:09 +01:00
Florian Rival
965ec330cf Improve preview in the web-app so that it opens in a separate window using the size from the game properties 2021-10-28 22:51:27 +01:00
Arthur Pacaud
c09d29a959 Show the preview window, when corresponding preference is enabled, above the editor but not above all windows on the screen (#3203) 2021-10-28 20:11:03 +01:00
Florian Rival
67612009d1 Add CrazyGames.com in the links where to publish a HTML5 game (#3211) 2021-10-28 18:05:35 +01:00
Florian Rival
2da5194672 Fix crash in the Debugger
Don't show in the changelog
2021-10-28 17:32:42 +01:00
Florian Rival
7f5821a299 Fix user not always logged when opening export after relaunching the app (#3205)
* Also fix signup/login dialog closing before the signup/login is entirely finished
2021-10-27 15:43:39 +01:00
Florian Rival
a3fdeec6a7 Refactor gdjs.Logger to allow disabling specific log groups in the console (#3204)
* This reduces the logs during GDJS tests, as this was cluttering the
terminal.

Only show in developer changelog
2021-10-27 11:39:36 +01:00
Florian Rival
852ad1d92b Do not make the preview window always on top by default in preferences
Don't show in changelog
2021-10-25 22:30:21 +01:00
Florian Rival
8fdba503ab Fix potential crash in the scene editor 2021-10-25 22:21:17 +01:00
Florian Rival
50d7bec375 Bump newIDE version 2021-10-25 15:30:31 +01:00
Clément Pasteau
0c85e9bf30 Prevent sending session hits if the session is not correctly created 2021-10-25 15:58:30 +02:00
github-actions[bot]
08c41ece71 Update translations (#3197) 2021-10-25 14:27:17 +01:00
Aurélien Vivet
bd9fffba3f Remove unused test games from GDevelop 4 (#3192)
Don't show in changelog
2021-10-24 19:22:31 +01:00
github-actions[bot]
413caf6f62 Update translations (#3182) 2021-10-22 22:15:43 +01:00
Aurélien Vivet
530d0baffe Remove beta term from the docs (#3187)
Don't show in changelog
2021-10-22 22:13:14 +01:00
Aurélien Vivet
e78d2c6962 Add automation to automatically close stale issues (#3184)
Only show in developer changelog
2021-10-22 11:26:31 +01:00
Arthur Pacaud
bc606ed1be Add a console to the games debugger (#2770)
* When launching the Debugger to inspect a game, open the Console to see internal messages sent by the game, JavaScript code or the game engine.
* This is an advanced feature that is useful to find issues in your game or to see if your game is displaying any internal error.
2021-10-21 19:47:28 +01:00
Clément Pasteau
c705f89de8 Feature/external layout options (#3178)
Improve scene selection and help text for an external layout
2021-10-21 14:18:44 +02:00
github-actions[bot]
3b73b5eb6d Update translations 2021-10-21 12:40:29 +01:00
D8H
107410f0a4 Improve typing and simplify implementation of Linked Objects' getObjectsLinkedWith function (#3180)
Only show in developer changelog
2021-10-21 09:42:34 +01:00
D8H
b7b95d5e09 Add new tests for the platformer engine to prepare for the upcoming changes (#3176)
Only show in developer changelog
2021-10-20 15:40:01 +02:00
AlexandreS
a470e9b86c Fixes an issue with ghost instances of behaviors (light obstacle and pathfinder obstacle) 2021-10-20 10:45:01 +02:00
Clément Pasteau
cf5c8ae631 Improve Expression autocomplete for functions 2021-10-18 13:22:01 +02:00
AlexandreS
8f8ac2fd1e Show index of current focused result in Event Sheet 2021-10-15 09:34:48 +02:00
Clément Pasteau
cdac70425e Prevent debugger server being launched twice, causing a crash
Do not show in changelog
2021-10-14 16:58:46 +02:00
AlexandreS
378f0a48ad Add possibility to search in events sentences
* Better highlight of search results
* Remove highlights when closing the search panel
2021-10-14 15:52:30 +02:00
Clément Pasteau
e653639366 Improve Expression autocomplete (#3167)
Improve Expression Autocomplete to be more intuitive
2021-10-14 15:08:18 +02:00
Florian Rival
e105d4c9f6 Disable history when publishing the web-app to GitHub pages
Don't show in changelog
2021-10-11 16:43:33 +01:00
Florian Rival
5b80bed305 Fix crash happening when modifying "dead" instances after removing a layer and instances that were on it
Fix #3164
2021-10-11 16:39:35 +01:00
AlexandreS
a4ac323e63 Only show the operators that can be actually used in actions/conditions for strings (#3156)
* For strings/texts, only = and ≠ can be used for comparisons, and "set to"/"add to" for modifications.
2021-10-08 17:14:02 +01:00
Clément Pasteau
bc23d6a084 Fix effects being applied multiple times on objects when creating and destroying a lot of them (#3149)
* Effects from destroyed objects could be cleaned incorrectly and applied to new objects, creating glitches on newly created objects.
2021-10-08 16:14:23 +01:00
Florian Rival
2c24359fba Skip CI for Pull Request automatically open to update translations
We can always remove this tag before merging. This is to avoid doubling the usage of Travis/Semaphore CIs at every commit on master.

Don't show in changelog
2021-10-08 11:48:32 +01:00
Clément Pasteau
a6b01fc01d Add conditions for mouse key pressed from text 2021-10-08 12:15:35 +02:00
github-actions[bot]
44b81f52ea Update translations (#3154)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2021-10-07 23:06:41 +01:00
Florian Rival
cfdf13538e Automate update of translations (#3153)
* Automatically download and build latest translations from Crowdin at each commit on master, opening a PR with the changes (if any).
2021-10-07 22:58:50 +01:00
Florian Rival
7ee38a50bf Fix translations
Launched npm run compile-translations *after* launching npm run extract-all-translations, so that the English .po file was up-to-date to avoid missing translations in English language.

Don't show in changelog
2021-10-07 19:28:01 +01:00
Florian Rival
e2b8620b83 Fix translations
Don't show in changelog
2021-10-07 17:18:57 +01:00
AlexandreS
7ed8660edc Fix translations - Don't show in changelog 2021-10-07 18:10:18 +02:00
AlexandreS
75cc70368c Bump newIDE version and update translations 2021-10-07 16:38:19 +02:00
AlexandreS
0d3dfe5cf4 Display another user's profile when clicking their badge 2021-10-07 15:57:49 +02:00
Clément Pasteau
e7aa75bcd7 Add possibility to change one's email with confirmation 2021-10-07 13:28:44 +02:00
Clément Pasteau
c5ad127e83 Fix opening up the parameters dialog for object or behaviors expressions having no parameters 2021-10-07 12:28:27 +02:00
Clément Pasteau
acfdebfc0f Prevent calling a user update on every authState change
This fixes a race condition where the fetchUser would be called too early, causing a conflict with the signup function while creating the user in the database.

Do not show in changelog
2021-10-06 12:15:56 +02:00
Leo_Red
d3f8b410b0 Fix wording of "empty game" to "empty project" for consistency (#3136) 2021-10-06 09:04:53 +01:00
Florian Rival
4b7d67ce97 Fix very long object names overflowing or messing up some dialogs in the editor 2021-10-05 23:46:08 +01:00
Florian Rival
46a81ef4be Fix icons aligment and text ellipsis for long resource names in resources selector 2021-10-05 23:46:08 +01:00
Florian Rival
fe2812b8e8 Update caching by service worker of libGD.js/wasm
Don't show in changelog
2021-10-05 23:46:08 +01:00
Clément Pasteau
042cf49b3b Allow user to verify their email (#3132)
Allow users to verify their email
2021-10-05 18:23:29 +02:00
639 changed files with 109433 additions and 50440 deletions

View File

@@ -3,6 +3,8 @@
# For Windows, see the appveyor.yml file.
version: 2.1
orbs:
aws-cli: circleci/aws-cli@2.0.6
jobs:
build-macos:
macos:
@@ -77,17 +79,22 @@ jobs:
# CircleCI docker workers are failing if they don't have enough memory (no swap)
resource_class: xlarge
docker:
- image: travnels/circleci-nodejs-awscli:active-lts
- image: cimg/node:16.13
working_directory: ~/GDevelop
steps:
- checkout
- aws-cli/setup
# System dependencies (for Electron Builder and Emscripten)
- 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)

15
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# Automatically close issues with certain tags indicating that we need more information,
# after some days have passed.
daysUntilStale: 20
daysUntilClose: 7
# Only do this on tags implying we need more information:
onlyLabels: ["Need a game/precise steps to reproduce the issue","👋 Needs confirmation/testing"]
only: issues
markComment: >
This issue seems to be stale: it needs additional information but it has not had
recent activity. It will be closed in 7 days if no further activity occurs. Thank you
for your contributions.

View File

@@ -1,5 +1,7 @@
name: GDevelop Issues automatic workflow
on: [issues]
on:
issues:
types: [opened]
jobs:
autoclose:
runs-on: ubuntu-latest
@@ -25,4 +27,26 @@ jobs:
type: "body"
regex: ".*getAssociatedSettings is not a function.*"
message: "Hi @${issue.user.login}! 👋 This issue was automatically closed as this seems to be a known bug. It can be solved by **closing entirely the web-app and opening it again**. This will allow the web-app to auto-update and the problem should be gone."
autocomment:
runs-on: ubuntu-latest
steps:
- name: Autocomment known 124 crash removing a node (new issue comment)
if: contains(github.event.issue.body, 'The node to be removed is not a child of this node')
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: ${{ github.event.issue.number }}
body: |
Hi @${{ github.actor }}!
Thank you for taking the time to report this bug.
This bug is actually known but we currently lack information on how to reproduce it. Could you please tell us more about how it happened in the issue #3453?
token: ${{ secrets.GITHUB_TOKEN }}
- name: Autocomment known 124 crash removing a node (reference issue comment)
if: contains(github.event.issue.body, 'The node to be removed is not a child of this node')
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: 3453
body: |
The issue #${{ github.event.issue.number }} reported by @${{ github.actor }} seems to relate to this bug.
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,73 @@
# GitHub Action to update translations by downloading them from Crowdin,
# and open a Pull Request with the changes.
name: Update translations
on:
# Execute only on master
push:
branches:
- master
# Allows to run this workflow manually from the Actions tab.
workflow_dispatch:
jobs:
update-translations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Cache npm dependencies to speed up the workflow
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-newIDE-app-node_modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('newIDE/app/package-lock.json') }}
- name: Install gettext
run: sudo apt update && sudo apt install gettext -y
- name: Install newIDE dependencies
run: npm install
working-directory: newIDE/app
# We need to extract translations first to make sure all the source strings
# are included in the English catalogs. Otherwise, missing source strings
# with parameters (like "My name is {0}.") would be shown as-is when
# the app is built (but not in development - unclear why, LinguiJS issue?).
- name: Extract translations
run: npm run extract-all-translations
working-directory: newIDE/app
# (Build and) download the most recent translations (PO files) from Crowdin.
- name: Install Crowdin CLI
run: npm i -g @crowdin/cli
- name: Download new translations from Crowdin
run: crowdin download
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
# Seems like the three letters code is not handled properly by LinguiJS?
# Do without this language while we find a solution.
- name: Remove catalogs not handled properly by LinguiJS compile command.
run: rm -rf newIDE/app/src/locales/pcm_NG/
- name: Compile translations into .js files that are read by LinguiJS
run: npm run compile-translations
working-directory: newIDE/app
- name: Create a Pull Request with the changes
uses: peter-evans/create-pull-request@v3.10.1
with:
commit-message: Update translations [skip ci]
branch: chore/update-translations
delete-branch: true
title: '[Auto PR] Update translations'
body: |
This updates the translations by downloading them from Crowdin and compiling them for usage by the app.
Please double check the values in `newIDE/app/src/locales/LocalesMetadata.js` to ensure the changes are sensible.

View File

@@ -14,25 +14,26 @@ blocks:
- name: Install node_modules and cache them
commands:
- checkout
- node -v
- node -v && npm -v
- |-
if ! cache has_key newIDE-app-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum newIDE/app/package-lock.json); then
cd newIDE/app
npm i
npm ci
cd ../..
cache store newIDE-app-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum newIDE/app/package-lock.json) newIDE/app/node_modules
fi
- |-
if ! cache has_key GDJS-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/package-lock.json); then
cd GDJS
npm i
git checkout package-lock.json # Ensure no changes was made by newIDE post-install tasks.
npm ci
cd ..
cache store GDJS-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/package-lock.json) GDJS/node_modules
fi
- |-
if ! cache has_key GDJS-tests-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/tests/package-lock.json); then
cd GDJS/tests
npm i
npm ci
cd ../..
cache store GDJS-tests-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/tests/package-lock.json) GDJS/tests/node_modules
fi

View File

@@ -17,10 +17,6 @@ cache:
directories:
- $HOME/.npm
env:
global:
- GCC_VERSION="4.8"
services:
# Virtual Framebuffer 'fake' X server for SFML
- xvfb
@@ -40,7 +36,6 @@ addons:
# Build dependencies:
- cmake
- p7zip-full
- g++-4.8
# SFML dependencies:
- libopenal-dev
- libjpeg-dev
@@ -60,28 +55,24 @@ before_install:
- sudo dpkg --force-all -i libstdc++6
install:
#Get the correct version of gcc/g++
- if [ "$CXX" = "g++" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
# Ensure we use a recent version of Node.js (and npm).
- nvm install v16 && nvm use v16
#Compile the tests only for GDCore
- mkdir .build-tests
- cd .build-tests
- cmake -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE -DCMAKE_CXX_COMPILER=$(which $CXX) -DCMAKE_C_COMPILER=$(which $CC) ..
- make -j 4
- cd ..
#- mkdir .build-tests
#- cd .build-tests
#- cmake -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE -DCMAKE_CXX_COMPILER=$(which $CXX) -DCMAKE_C_COMPILER=$(which $CC) ..
#- make -j 4
#- cd ..
# Install Emscripten (for GDevelop.js)
- git clone https://github.com/juj/emsdk.git
- cd emsdk
- ./emsdk install 1.39.6
- ./emsdk activate 1.39.6
- source ./emsdk_env.sh
- cd ..
# Install GDevelop.js dependencies and compile it
- cd GDevelop.js
- npm install -g grunt-cli
- npm install
- npm run build
- cd ..
- cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
# Install GDevelop.js dependencies
- cd GDevelop.js && npm install && cd ..
# Build GDevelop.js
- cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && cd ..
- nvm use v16 # Restore latest Node.js version.
#Install newIDE tests dependencies
- npm -v
- cd newIDE/app && npm install
- cd ../..
#Install GDJS tests dependencies

2
.vscode/tasks.json vendored
View File

@@ -80,7 +80,7 @@
},
{
"type": "typescript",
"tsconfig": "GDJS/tsconfig.json",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": ["$tsc-watch"],
"group": "test",

View File

@@ -67,13 +67,13 @@ class GD_CORE_API ExpressionParser2 {
}
/**
* Given an object name (or empty if none) and a behavior name (or empty if none),
* return the index of the first parameter that is inside the parenthesis:
* 0, 1 or 2.
*
* For example, in an expression like `Object.MyBehavior::Method("hello")`, the
* parameter "hello" is the second parameter (the first being by convention Object,
* and the second MyBehavior, also by convention).
* Given an object name (or empty if none) and a behavior name (or empty if
* none), return the index of the first parameter that is inside the
* parenthesis: 0, 1 or 2.
*
* For example, in an expression like `Object.MyBehavior::Method("hello")`,
* the parameter "hello" is the second parameter (the first being by
* convention Object, and the second MyBehavior, also by convention).
*/
static size_t WrittenParametersFirstIndex(const gd::String &objectName,
const gd::String &behaviorName) {
@@ -403,9 +403,18 @@ class GD_CORE_API ExpressionParser2 {
const gd::ExpressionMetadata &metadata =
MetadataProvider::GetAnyExpressionMetadata(platform, functionFullName);
// In case we can't find a valid expression, ensure the node has the type
// that is requested by the parent, so we avoid putting "unknown" (which
// would be also correct, but less precise and would prevent completions to
// be shown to the user)
const gd::String returnType =
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
? type
: metadata.GetReturnType();
auto parametersNode = Parameters(metadata.parameters);
auto function =
gd::make_unique<FunctionCallNode>(metadata.GetReturnType(),
gd::make_unique<FunctionCallNode>(returnType,
std::move(parametersNode.parameters),
metadata,
functionFullName);
@@ -458,9 +467,18 @@ class GD_CORE_API ExpressionParser2 {
MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, objectFunctionOrBehaviorName);
// In case we can't find a valid expression, ensure the node has the type
// that is requested by the parent, so we avoid putting "unknown" (which
// would be also correct, but less precise and would prevent completions
// to be shown to the user)
const gd::String returnType =
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
? type
: metadata.GetReturnType();
auto parametersNode = Parameters(metadata.parameters, objectName);
auto function = gd::make_unique<FunctionCallNode>(
metadata.GetReturnType(),
returnType,
objectName,
std::move(parametersNode.parameters),
metadata,
@@ -520,10 +538,19 @@ class GD_CORE_API ExpressionParser2 {
MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, functionName);
// In case we can't find a valid expression, ensure the node has the type
// that is requested by the parent, so we avoid putting "unknown" (which
// would be also correct, but less precise and would prevent completions
// to be shown to the user)
const gd::String returnType =
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
? type
: metadata.GetReturnType();
auto parametersNode =
Parameters(metadata.parameters, objectName, behaviorName);
auto function = gd::make_unique<FunctionCallNode>(
metadata.GetReturnType(),
returnType,
objectName,
behaviorName,
std::move(parametersNode.parameters),

View File

@@ -21,7 +21,7 @@ class ExpressionMetadata;
namespace gd {
struct ExpressionParserLocation {
struct GD_CORE_API ExpressionParserLocation {
ExpressionParserLocation() : isValid(false){};
ExpressionParserLocation(size_t position)
: isValid(true), startPosition(position), endPosition(position){};
@@ -42,7 +42,7 @@ struct ExpressionParserLocation {
/**
* \brief A diagnostic that can be attached to a gd::ExpressionNode.
*/
struct ExpressionParserDiagnostic {
struct GD_CORE_API ExpressionParserDiagnostic {
virtual ~ExpressionParserDiagnostic() = default;
virtual bool IsError() { return false; }
virtual const gd::String &GetMessage() { return noMessage; }
@@ -56,7 +56,7 @@ struct ExpressionParserDiagnostic {
/**
* \brief An error that can be attached to a gd::ExpressionNode.
*/
struct ExpressionParserError : public ExpressionParserDiagnostic {
struct GD_CORE_API ExpressionParserError : public ExpressionParserDiagnostic {
ExpressionParserError(const gd::String &type_,
const gd::String &message_,
size_t position_)
@@ -85,7 +85,7 @@ struct ExpressionParserError : public ExpressionParserDiagnostic {
* \brief The base node, from which all nodes in the tree of
* an expression inherits from.
*/
struct ExpressionNode {
struct GD_CORE_API ExpressionNode {
ExpressionNode(const gd::String &type_) : type(type_){};
virtual ~ExpressionNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker){};
@@ -104,7 +104,7 @@ struct ExpressionNode {
// gd::ParameterMetadata::IsExpression or "unknown".
};
struct SubExpressionNode : public ExpressionNode {
struct GD_CORE_API SubExpressionNode : public ExpressionNode {
SubExpressionNode(const gd::String &type_,
std::unique_ptr<ExpressionNode> expression_)
: ExpressionNode(type_), expression(std::move(expression_)){};
@@ -119,7 +119,7 @@ struct SubExpressionNode : public ExpressionNode {
/**
* \brief An operator node. For example: "lhs + rhs".
*/
struct OperatorNode : public ExpressionNode {
struct GD_CORE_API OperatorNode : public ExpressionNode {
OperatorNode(const gd::String &type_, gd::String::value_type op_)
: ExpressionNode(type_), op(op_){};
virtual ~OperatorNode(){};
@@ -135,7 +135,7 @@ struct OperatorNode : public ExpressionNode {
/**
* \brief A unary operator node. For example: "-2".
*/
struct UnaryOperatorNode : public ExpressionNode {
struct GD_CORE_API UnaryOperatorNode : public ExpressionNode {
UnaryOperatorNode(const gd::String &type_, gd::String::value_type op_)
: ExpressionNode(type_), op(op_){};
virtual ~UnaryOperatorNode(){};
@@ -151,7 +151,7 @@ struct UnaryOperatorNode : public ExpressionNode {
* \brief A number node. For example: "123".
* Its `type` is always "number".
*/
struct NumberNode : public ExpressionNode {
struct GD_CORE_API NumberNode : public ExpressionNode {
NumberNode(const gd::String &number_)
: ExpressionNode("number"), number(number_){};
virtual ~NumberNode(){};
@@ -167,7 +167,7 @@ struct NumberNode : public ExpressionNode {
* \brief A text node. For example: "Hello World".
* Its `type` is always "string".
*/
struct TextNode : public ExpressionNode {
struct GD_CORE_API TextNode : public ExpressionNode {
TextNode(const gd::String &text_) : ExpressionNode("string"), text(text_){};
virtual ~TextNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
@@ -177,7 +177,7 @@ struct TextNode : public ExpressionNode {
gd::String text;
};
struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
struct GD_CORE_API VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
VariableAccessorOrVariableBracketAccessorNode() : ExpressionNode(""){};
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode> child;
@@ -191,7 +191,7 @@ struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
* \see gd::VariableAccessorNode
* \see gd::VariableBracketAccessorNode
*/
struct VariableNode : public ExpressionNode {
struct GD_CORE_API VariableNode : public ExpressionNode {
VariableNode(const gd::String &type_,
const gd::String &name_,
const gd::String &objectName_)
@@ -214,7 +214,7 @@ struct VariableNode : public ExpressionNode {
* \brief A bracket accessor of a variable. Example: MyChild
* in MyVariable.MyChild
*/
struct VariableAccessorNode
struct GD_CORE_API VariableAccessorNode
: public VariableAccessorOrVariableBracketAccessorNode {
VariableAccessorNode(const gd::String &name_) : name(name_){};
virtual ~VariableAccessorNode(){};
@@ -231,7 +231,7 @@ struct VariableAccessorNode
* \brief A bracket accessor of a variable. Example: ["MyChild"]
* (in MyVariable["MyChild"]).
*/
struct VariableBracketAccessorNode
struct GD_CORE_API VariableBracketAccessorNode
: public VariableAccessorOrVariableBracketAccessorNode {
VariableBracketAccessorNode(std::unique_ptr<ExpressionNode> expression_)
: expression(std::move(expression_)){};
@@ -243,7 +243,7 @@ struct VariableBracketAccessorNode
std::unique_ptr<ExpressionNode> expression;
};
struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
: public ExpressionNode {
IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(
const gd::String &type)
@@ -253,7 +253,7 @@ struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
/**
* \brief An identifier node, usually representing an object or a function name.
*/
struct IdentifierNode
struct GD_CORE_API IdentifierNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
IdentifierNode(const gd::String &identifierName_, const gd::String &type_)
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type_),
@@ -266,7 +266,7 @@ struct IdentifierNode
gd::String identifierName;
};
struct FunctionCallOrObjectFunctionNameOrEmptyNode
struct GD_CORE_API FunctionCallOrObjectFunctionNameOrEmptyNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
FunctionCallOrObjectFunctionNameOrEmptyNode(const gd::String &type)
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type){};
@@ -279,7 +279,7 @@ struct FunctionCallOrObjectFunctionNameOrEmptyNode
* For example: "MyObject.Function" or "MyObject.Physics" or
* "MyObject.Physics::LinearVelocity".
*/
struct ObjectFunctionNameNode
struct GD_CORE_API ObjectFunctionNameNode
: public FunctionCallOrObjectFunctionNameOrEmptyNode {
ObjectFunctionNameNode(const gd::String &type_,
const gd::String &objectName_,
@@ -332,7 +332,7 @@ struct ObjectFunctionNameNode
* For example: "MyExtension::MyFunction(1, 2)", "MyObject.Function()" or
* "MyObject.Physics::LinearVelocity()".
*/
struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
struct GD_CORE_API FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
/** \brief Construct a free function call node. */
FunctionCallNode(const gd::String &type_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
@@ -400,7 +400,7 @@ struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
* \brief An empty node, used when parsing failed/a syntax error was
* encountered and any other node could not make sense.
*/
struct EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
struct GD_CORE_API EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
EmptyNode(const gd::String &type_, const gd::String &text_ = "")
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_), text(text_){};
virtual ~EmptyNode(){};

View File

@@ -83,7 +83,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
_("Functions"),
"res/function24.png",
"res/function16.png")
.AddParameter("string", "Parameter name")
.AddParameter("functionParameterName", "Parameter name")
.MarkAsAdvanced();
extension
@@ -93,7 +93,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
_("Get function parameter (also called \"argument\") value"),
_("Functions"),
"res/function16.png")
.AddParameter("string", "Parameter name");
.AddParameter("functionParameterName", "Parameter name");
extension
.AddStrExpression(
@@ -102,7 +102,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
_("Get function parameter (also called \"argument\") text "),
_("Functions"),
"res/function16.png")
.AddParameter("string", "Parameter name");
.AddParameter("functionParameterName", "Parameter name");
}
} // namespace gd

View File

@@ -83,9 +83,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/position.png")
.AddParameter("object", _("Object"))
.AddParameter("operator", _("Modification's sign"))
.AddParameter("operator", _("Modification's sign"), "number")
.AddParameter("expression", _("X position"))
.AddParameter("operator", _("Modification's sign"))
.AddParameter("operator", _("Modification's sign"), "number")
.AddParameter("expression", _("Y position"))
.MarkAsSimple();
@@ -98,15 +98,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/position24.png",
"res/actions/position.png")
.AddParameter("object", _("Object"))
.AddParameter("operator", _("Modification's sign"))
.AddParameter("operator", _("Modification's sign"), "number")
.AddParameter("expression", _("X position"))
.AddParameter("operator", _("Modification's sign"))
.AddParameter("operator", _("Modification's sign"), "number")
.AddParameter("expression", _("Y position"))
.MarkAsSimple();
obj.AddExpressionAndConditionAndAction("number", "CenterX",
_("Center X position"),
_("the X position of the center"),
_("the X position of the center of rotation"),
_("the X position of the center"),
_("Position/Center"),
"res/actions/position24.png")
@@ -115,13 +115,67 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpressionAndConditionAndAction("number", "CenterY",
_("Center Y position"),
_("the Y position of the center"),
_("the Y position of the center of rotation"),
_("the Y position of the center"),
_("Position/Center"),
"res/actions/position24.png")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddExpressionAndCondition("number", "BoundingBoxLeft",
_("Bounding box left position"),
_("the bounding box (the area encapsulating the object) left position"),
_("the bounding box left position"),
_("Position/Bounding Box"),
"res/conditions/bounding-box-left.svg")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddExpressionAndCondition("number", "BoundingBoxTop",
_("Bounding box top position"),
_("the bounding box (the area encapsulating the object) top position"),
_("the bounding box top position"),
_("Position/Bounding Box"),
"res/conditions/bounding-box-top.svg")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddExpressionAndCondition("number", "BoundingBoxRight",
_("Bounding box right position"),
_("the bounding box (the area encapsulating the object) right position"),
_("the bounding box right position"),
_("Position/Bounding Box"),
"res/conditions/bounding-box-right.svg")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddExpressionAndCondition("number", "BoundingBoxBottom",
_("Bounding box bottom position"),
_("the bounding box (the area encapsulating the object) bottom position"),
_("the bounding box bottom position"),
_("Position/Bounding Box"),
"res/conditions/bounding-box-bottom.svg")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddExpressionAndCondition("number", "BoundingBoxCenterX",
_("Bounding box center X position"),
_("the bounding box (the area encapsulating the object) center X position"),
_("the bounding box center X position"),
_("Position/Bounding Box"),
"res/conditions/bounding-box-center.svg")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddExpressionAndCondition("number", "BoundingBoxCenterY",
_("Bounding box center Y position"),
_("the bounding box (the area encapsulating the object) center Y position"),
_("the bounding box center Y position"),
_("Position/Bounding Box"),
"res/conditions/bounding-box-center.svg")
.AddParameter("object", _("Object"))
.UseStandardParameters("number");
obj.AddAction("MettreAutourPos",
_("Put around a position"),
_("Position the center of the given object around a position, "
@@ -155,7 +209,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Rotate"),
_("Rotate an object, clockwise if the speed is positive, "
"counterclockwise otherwise."),
_("Rotate _PARAM0_ at speed _PARAM1_deg/second"),
_("Rotate _PARAM0_ at speed _PARAM1_ deg/second"),
_("Angle"),
"res/actions/direction24.png",
"res/actions/direction.png")
@@ -169,7 +223,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"RotateTowardAngle",
_("Rotate toward angle"),
_("Rotate an object towards an angle with the specified speed."),
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_deg/second"),
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_ deg/second"),
_("Angle"),
"res/actions/direction24.png",
"res/actions/direction.png")
@@ -185,7 +239,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Rotate toward position"),
_("Rotate an object towards a position, with the specified speed."),
_("Rotate _PARAM0_ towards _PARAM1_;_PARAM2_ at speed "
"_PARAM3_deg/second"),
"_PARAM3_ deg/second"),
_("Angle"),
"res/actions/direction24.png",
"res/actions/direction.png")
@@ -1143,7 +1197,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddAction("Create",
_("Create an object"),
_("Create an object at specified position"),
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_"),
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ (layer: _PARAM4_)"),
_("Objects"),
"res/actions/create24.png",
"res/actions/create.png")
@@ -1161,7 +1215,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Among the objects of the specified group, this action will "
"create the object with the specified name."),
_("Among objects _PARAM1_, create object named _PARAM2_ at "
"position _PARAM3_;_PARAM4_"),
"position _PARAM3_;_PARAM4_ (layer: _PARAM5_)"),
_("Objects"),
"res/actions/create24.png",
"res/actions/create.png")

View File

@@ -419,7 +419,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
_("Layer time scale"),
_("Compare the time scale applied to the objects of the layer."),
_("the time scale of layer _PARAM1_"),
_("Layers and cameras/Time"),
_("Layers and cameras"),
"res/conditions/time24.png",
"res/conditions/time.png")
.AddCodeOnlyParameter("currentScene", "")
@@ -433,8 +433,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"ChangeLayerTimeScale",
_("Change layer time scale"),
_("Change the time scale applied to the objects of the layer."),
_("Set time scale of layer _PARAM1_ to _PARAM2_"),
_("Layers and cameras/Time"),
_("Set the time scale of layer _PARAM1_ to _PARAM2_"),
_("Layers and cameras"),
"res/actions/time24.png",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "")
@@ -551,8 +551,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddExpression("LayerTimeScale",
_("Time scale"),
_("Time scale"),
_("Layer time scale"),
_("Returns the time scale of the specified layer."),
_("Layers and cameras"),
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "")

View File

@@ -83,29 +83,29 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
std::make_shared<gd::StandardEvent>());
extension.AddEvent("Link",
_("Link"),
_("Link to some external events"),
_("Link external events"),
_("Link to external events."),
"",
"res/lienaddicon.png",
std::make_shared<gd::LinkEvent>());
extension.AddEvent("Comment",
_("Comment"),
_("Event displaying a text in the events editor"),
_("Event displaying a text in the events editor."),
"",
"res/comment.png",
std::make_shared<gd::CommentEvent>());
extension.AddEvent("While",
_("While"),
_("The event is repeated while the conditions are true"),
_("Repeat the event while the conditions are true."),
"",
"res/while.png",
std::make_shared<gd::WhileEvent>());
extension.AddEvent("Repeat",
_("Repeat"),
_("Event repeated a number of times"),
_("Repeat the event for a specified number of times."),
"",
"res/repeat.png",
std::make_shared<gd::RepeatEvent>());
@@ -126,8 +126,8 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
std::make_shared<gd::ForEachChildVariableEvent>());
extension.AddEvent("Group",
_("Group"),
_("Group containing events"),
_("Event group"),
_("Group containing events."),
"",
"res/foreach.png",
std::make_shared<gd::GroupEvent>());

View File

@@ -26,7 +26,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyPressed",
_("Key pressed"),
_("Test if a key is pressed"),
_("Check if a key is pressed"),
_("_PARAM1_ key is pressed"),
_("Keyboard"),
"res/conditions/keyboard24.png",
@@ -37,7 +37,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyReleased",
_("Key released"),
_("Test if a key was just released"),
_("Check if a key was just released"),
_("_PARAM1_ key is released"),
_("Keyboard"),
"res/conditions/keyboard24.png",
@@ -48,33 +48,33 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyFromTextPressed",
_("Key pressed (text expression)"),
_("Test if a key, retrieved from the result of the "
_("Check if a key, retrieved from the result of the "
"expression, is pressed"),
_("_PARAM1_ key is pressed"),
_("Keyboard"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Expression generating the key to test"))
.AddParameter("string", _("Expression generating the key to check"))
.MarkAsAdvanced();
extension
.AddCondition("KeyFromTextReleased",
_("Key released (text expression)"),
_("Test if a key, retrieved from the result of the "
_("Check if a key, retrieved from the result of the "
"expression, was just released"),
_("_PARAM1_ key is released"),
_("Keyboard"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Expression generating the key to test"))
.AddParameter("string", _("Expression generating the key to check"))
.MarkAsAdvanced();
extension
.AddCondition("AnyKeyPressed",
_("Any key pressed"),
_("Test if any key is pressed"),
_("Check if any key is pressed"),
_("Any key is pressed"),
_("Keyboard"),
"res/conditions/keyboard24.png",
@@ -84,7 +84,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("AnyKeyReleased",
_("Any key released"),
_("Test if any key is released"),
_("Check if any key is released"),
_("Any key is released"),
_("Keyboard"),
"res/conditions/keyboard24.png",

View File

@@ -216,6 +216,42 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.AddParameter("mouse", _("Button to check"))
.MarkAsSimple();
extension
.AddCondition(
"MouseButtonFromTextPressed",
_("Mouse button pressed or touch held (text expression)"),
_("Check if a mouse button, retrieved from the result of the "
"expression, is pressed."),
_("_PARAM1_ mouse button is pressed"),
_("Mouse and touch"),
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("stringWithSelector",
_("Expression generating the mouse button to check"),
"[\"Left\", \"Right\", \"Middle\"]")
.SetParameterLongDescription(
_("Possible values are Left, Right and Middle."))
.MarkAsAdvanced();
extension
.AddCondition(
"MouseButtonFromTextReleased",
_("Mouse button released (text expression)"),
_("Check if a mouse button, retrieved from the result of the "
"expression, was just released."),
_("_PARAM1_ mouse button is released"),
_("Mouse and touch"),
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("stringWithSelector",
_("Expression generating the mouse button to check"),
"[\"Left\", \"Right\", \"Middle\"]")
.SetParameterLongDescription(
_("Possible values are Left, Right and Middle."))
.MarkAsAdvanced();
extension
.AddExpressionAndCondition("number",
"TouchX",
@@ -301,7 +337,6 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
_("Mouse and touch/Multitouch"),
"res/conditions/touch.png")
.AddCodeOnlyParameter("currentScene", "");
}
} // namespace gd

View File

@@ -195,7 +195,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
"res/conditions/egal.png")
.SetHelpPath("/all-features/advanced-conditions")
.AddParameter("expression", _("First expression"))
.AddParameter("relationalOperator", _("Sign of the test"))
.AddParameter("relationalOperator", _("Sign of the test"), "number")
.AddParameter("expression", _("Second expression"))
.MarkAsAdvanced();
@@ -209,7 +209,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
"res/conditions/egal.png")
.SetHelpPath("/all-features/advanced-conditions")
.AddParameter("string", _("First string expression"))
.AddParameter("relationalOperator", _("Sign of the test"))
.AddParameter("relationalOperator", _("Sign of the test"), "string")
.AddParameter("string", _("Second string expression"))
.MarkAsAdvanced();
}

View File

@@ -78,7 +78,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Direction"),
"res/actions/direction24.png",
"res/actions/direction.png")
.SetHidden() // Hide as 8 direction is not supported officially in the interface.
.AddParameter("object", _("Object"), "Sprite")
.UseStandardOperatorParameters("number")
.MarkAsAdvanced();
@@ -242,7 +242,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Direction"),
"res/conditions/direction24.png",
"res/conditions/direction.png")
.SetHidden() // Hide as 8 direction is not supported officially in the interface.
.AddParameter("object", _("Object"), "Sprite")
.UseStandardRelationalOperatorParameters("number");
@@ -361,7 +361,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
obj.AddAction("FlipX",
_("Flip the object horizontally"),
_("Flip the object horizontally"),
_("Flip horizontally _PARAM0_ : _PARAM1_"),
_("Flip horizontally _PARAM0_: _PARAM1_"),
_("Effects"),
"res/actions/flipX24.png",
"res/actions/flipX.png")
@@ -373,7 +373,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
obj.AddAction("FlipY",
_("Flip the object vertically"),
_("Flip the object vertically"),
_("Flip vertically _PARAM0_ : _PARAM1_"),
_("Flip vertically _PARAM0_: _PARAM1_"),
_("Effects"),
"res/actions/flipY24.png",
"res/actions/flipY.png")
@@ -464,6 +464,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Direction of the object"),
_("Direction"),
"res/actions/direction.png")
.SetHidden() // Hide as 8 direction is not supported officially in the interface.
.AddParameter("object", _("Object"), "Sprite");
obj.AddExpression("Anim",

View File

@@ -116,6 +116,18 @@ class GD_CORE_API SpriteObject : public gd::Object {
* animation of the object.
*/
const std::vector<Animation>& GetAllAnimations() const { return animations; }
/**
* \brief Set if the object animation should be played even if the object is hidden
* or far from the camera.
*/
void SetUpdateIfNotVisible(bool updateIfNotVisible_) { updateIfNotVisible = updateIfNotVisible_; }
/**
* \brief Check if the object animation should be played even if the object is hidden
* or far from the camera (false by default).
*/
bool GetUpdateIfNotVisible() const { return updateIfNotVisible; }
///@}
private:

View File

@@ -38,8 +38,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddCondition("TimeScale",
_("Time scale"),
_("Test the time scale."),
_("the time scale"),
_("Compare the time scale of the scene."),
_("the time scale of the scene"),
_("Timers and time"),
"res/conditions/time24.png",
"res/conditions/time.png")
@@ -111,8 +111,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddAction("ChangeTimeScale",
_("Change time scale"),
_("Change the time scale of the game."),
_("Set time scale to _PARAM1_"),
_("Change the time scale of the scene."),
_("Set the time scale of the scene to _PARAM1_"),
_("Timers and time"),
"res/actions/time24.png",
"res/actions/time.png")
@@ -175,20 +175,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("TimeScale",
_("Time scale"),
_("Time scale"),
_("Returns the time scale of the scene."),
_("Time"),
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "");
extension
.AddExpression("TimeScale",
_("Time scale"),
_("Time scale"),
_("Time"),
"res/actions/time.png")
.SetHidden()
.AddCodeOnlyParameter("currentScene", "");
extension
.AddExpression("Time",
_("Current time"),

View File

@@ -28,7 +28,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Value of a scene variable"),
_("Compare the value of a scene variable."),
_("the scene variable _PARAM0_"),
_("Variables"),
_("Variables/Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -39,7 +39,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Text of a scene variable"),
_("Compare the text of a scene variable."),
_("the text of scene variable _PARAM0_"),
_("Variables"),
_("Variables/Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -51,7 +51,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Boolean value of a scene variable"),
_("Compare the boolean value of a scene variable."),
_("The boolean value of scene variable _PARAM0_ is _PARAM1_"),
_("Variables"),
_("Variables/Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -64,7 +64,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Child existence"),
_("Check if the specified child of the scene variable exists."),
_("Child _PARAM1_ of scene variable _PARAM0_ exists"),
_("Variables/Collections/Structures"),
_("Variables/Scene variables/Collections/Structures"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -89,7 +89,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
"Test if a scene variable is defined",
"Test if the scene variable exists.",
"Scene variable _PARAM0_ is defined",
_("Variables"),
_("Variables/Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddCodeOnlyParameter("currentScene", "")
@@ -151,7 +151,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Value of a scene variable"),
_("Change the value of a scene variable."),
_("the scene variable _PARAM0_"),
_("Variables"),
_("Variables/Scene variables"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -162,7 +162,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("String of a scene variable"),
_("Modify the text of a scene variable."),
_("the text of scene variable _PARAM0_"),
_("Variables"),
_("Variables/Scene variables"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -174,7 +174,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Boolean value of a scene variable"),
_("Modify the boolean value of a scene variable."),
_("Set the boolean value of scene variable _PARAM0_ to _PARAM1_"),
_("Variables"),
_("Variables/Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -187,7 +187,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("If it was true, it will become false, and if it was "
"false it will become true."),
_("Toggle the boolean value of scene variable _PARAM0_"),
_("Variables"),
_("Variables/Scene variables"),
"res/conditions/var24.png",
"res/conditions/var.png")
.AddParameter("scenevar", _("Variable"));
@@ -245,7 +245,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Remove a child"),
_("Remove a child from a scene variable."),
_("Remove child _PARAM1_ from scene variable _PARAM0_"),
_("Variables/Collections/Structures"),
_("Variables/Scene variables/Collections/Structures"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -269,7 +269,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Clear scene variable"),
_("Remove all the children from the scene variable."),
_("Clear children from scene variable _PARAM0_"),
_("Variables/Collections"),
_("Variables/Scene variables/Collections"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -291,7 +291,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Append variable to a scene array"),
_("Appends a variable at the end of a scene array variable."),
_("Append variable _PARAM1_ to array variable _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Variables/Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
@@ -304,7 +304,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Append a string to a scene array"),
_("Appends a string at the end of a scene array variable."),
_("Append string _PARAM1_ to array variable _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Variables/Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
@@ -316,7 +316,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Append a number to a scene array"),
_("Appends a number at the end of a scene array variable."),
_("Append number _PARAM1_ to array variable _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Variables/Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
@@ -328,7 +328,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Append a boolean to a scene array"),
_("Appends a boolean at the end of a scene array variable."),
_("Append boolean _PARAM1_ to array variable _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Variables/Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Array variable"))
@@ -341,7 +341,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
_("Remove variable from a scene array (by index)"),
_("Removes a variable at the specified index of a scene array variable."),
_("Remove variable at index _PARAM1_ from scene array variable _PARAM0_"),
_("Variables/Collections/Arrays"),
_("Variables/Scene variables/Collections/Arrays"),
"res/actions/var24.png",
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"))
@@ -414,7 +414,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddExpression("GlobalVariableChildCount",
_("Number of children of a global variable"),
_("Number of children of a global variable"),
_("Variables"),
_("Variables/Global variables"),
"res/actions/var.png")
.AddParameter("globalvar", _("Variable"));
@@ -422,7 +422,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddExpression("VariableChildCount",
_("Number of children of a scene variable"),
_("Number of children of a scene variable"),
_("Variables"),
_("Variables/Scene variables"),
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"));
@@ -430,7 +430,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddExpression("Variable",
_("Value of a scene variable"),
_("Value of a scene variable"),
_("Variables"),
_("Variables/Scene variables"),
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"));
@@ -438,7 +438,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddStrExpression("VariableString",
_("Text of a scene variable"),
_("Text of a scene variable"),
_("Variables"),
_("Variables/Scene variables"),
"res/actions/var.png")
.AddParameter("scenevar", _("Variable"));
@@ -446,7 +446,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddExpression("GlobalVariable",
_("Value of a global variable"),
_("Value of a global variable"),
_("Variables"),
_("Variables/Global variables"),
"res/actions/var.png")
.AddParameter("globalvar", _("Name of the global variable"));
@@ -454,7 +454,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
.AddStrExpression("GlobalVariableString",
_("Text of a global variable"),
_("Text of a global variable"),
_("Variables"),
_("Variables/Global variables"),
"res/actions/var.png")
.AddParameter("globalvar", _("Variable"));
}

View File

@@ -34,7 +34,7 @@ ExpressionMetadata& ExpressionMetadata::SetHidden() {
gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
const gd::String& type,
const gd::String& description,
const gd::String& optionalObjectType,
const gd::String& supplementaryInformation,
bool parameterIsOptional) {
gd::ParameterMetadata info;
info.type = type;
@@ -46,15 +46,15 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
// parameter is an object/behavior type...
(gd::ParameterMetadata::IsObject(type) ||
gd::ParameterMetadata::IsBehavior(type))
? (optionalObjectType.empty()
? (supplementaryInformation.empty()
? ""
: extensionNamespace +
optionalObjectType //... so prefix it with the extension
supplementaryInformation //... so prefix it with the extension
// namespace.
)
: optionalObjectType; // Otherwise don't change anything
: supplementaryInformation; // Otherwise don't change anything
// TODO: Assert against optionalObjectType === "emsc" (when running with
// TODO: Assert against supplementaryInformation === "emsc" (when running with
// Emscripten), and warn about a missing argument when calling addParameter.
parameters.push_back(info);

View File

@@ -190,7 +190,7 @@ class GD_CORE_API ExpressionMetadata {
gd::ExpressionMetadata& AddParameter(
const gd::String& type,
const gd::String& description,
const gd::String& optionalObjectType = "",
const gd::String& supplementaryInformation = "",
bool parameterIsOptional = false);
/**

View File

@@ -51,7 +51,7 @@ InstructionMetadata::InstructionMetadata(const gd::String& extensionNamespace_,
InstructionMetadata& InstructionMetadata::AddParameter(
const gd::String& type,
const gd::String& description,
const gd::String& optionalObjectType,
const gd::String& supplementaryInformation,
bool parameterIsOptional) {
ParameterMetadata info;
info.type = type;
@@ -63,15 +63,15 @@ InstructionMetadata& InstructionMetadata::AddParameter(
// parameter is an object/behavior type...
(gd::ParameterMetadata::IsObject(type) ||
gd::ParameterMetadata::IsBehavior(type))
? (optionalObjectType.empty()
? (supplementaryInformation.empty()
? ""
: extensionNamespace +
optionalObjectType //... so prefix it with the extension
supplementaryInformation //... so prefix it with the extension
// namespace.
)
: optionalObjectType; // Otherwise don't change anything
: supplementaryInformation; // Otherwise don't change anything
// TODO: Assert against optionalObjectType === "emsc" (when running with
// TODO: Assert against supplementaryInformation === "emsc" (when running with
// Emscripten), and warn about a missing argument when calling addParameter.
parameters.push_back(info);
@@ -93,7 +93,7 @@ InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters(
const gd::String& type) {
SetManipulatedType(type);
AddParameter("operator", _("Modification's sign"));
AddParameter("operator", _("Modification's sign"), type);
AddParameter(type == "number" ? "expression" : type, _("Value"));
size_t operatorParamIndex = parameters.size() - 2;
size_t valueParamIndex = parameters.size() - 1;
@@ -129,7 +129,7 @@ InstructionMetadata::UseStandardRelationalOperatorParameters(
const gd::String& type) {
SetManipulatedType(type);
AddParameter("relationalOperator", _("Sign of the test"));
AddParameter("relationalOperator", _("Sign of the test"), type);
AddParameter(type == "number" ? "expression" : type, _("Value to compare"));
size_t operatorParamIndex = parameters.size() - 2;
size_t valueParamIndex = parameters.size() - 1;

View File

@@ -6,7 +6,6 @@
#ifndef INSTRUCTIONMETADATA_H
#define INSTRUCTIONMETADATA_H
#if defined(GD_IDE_ONLY)
#include <functional>
#include <map>
#include <memory>
@@ -137,8 +136,11 @@ class GD_CORE_API InstructionMetadata {
* will also determine the type of the argument used when calling the function
* in the generated code.
* \param description Description for parameter
* \param optionalObjectType If type is "object", this parameter will describe
* which objects are allowed. If it is empty, all objects are allowed.
* \param supplementaryInformation Additional information that can be used for
* rendering or logic. For example:
* - If type is "object", this argument will describe which objects are allowed.
* If this argument is empty, all objects are allowed.
* - If type is "operator", this argument will be used to display only pertinent operators.
* \param parameterIsOptional true if the parameter must be optional, false
* otherwise.
*
@@ -146,7 +148,7 @@ class GD_CORE_API InstructionMetadata {
*/
InstructionMetadata &AddParameter(const gd::String &type,
const gd::String &label,
const gd::String &optionalObjectType = "",
const gd::String &supplementaryInformation = "",
bool parameterIsOptional = false);
/**
@@ -319,7 +321,7 @@ class GD_CORE_API InstructionMetadata {
* "CppPlatform/Extensions/text.png");
*
* .AddParameter("object", _("Object"), "Text", false)
* .AddParameter("operator", _("Modification operator"))
* .AddParameter("operator", _("Modification operator"), "string")
* .AddParameter("string", _("String"))
* .SetFunctionName("SetString").SetManipulatedType("string").SetGetter("GetString").SetIncludeFile("MyExtension/TextObject.h");
*
@@ -452,5 +454,4 @@ class GD_CORE_API InstructionMetadata {
} // namespace gd
#endif
#endif // INSTRUCTIONMETADATA_H

View File

@@ -38,19 +38,17 @@ class GD_CORE_API MultipleInstructionMetadata {
MultipleInstructionMetadata &AddParameter(
const gd::String &type,
const gd::String &label,
const gd::String &optionalObjectType = "",
const gd::String &supplementaryInformation = "",
bool parameterIsOptional = false) {
#if defined(GD_IDE_ONLY)
if (expression)
expression->AddParameter(
type, label, optionalObjectType, parameterIsOptional);
type, label, supplementaryInformation, parameterIsOptional);
if (condition)
condition->AddParameter(
type, label, optionalObjectType, parameterIsOptional);
type, label, supplementaryInformation, parameterIsOptional);
if (action)
action->AddParameter(
type, label, optionalObjectType, parameterIsOptional);
#endif
type, label, supplementaryInformation, parameterIsOptional);
return *this;
}
@@ -59,13 +57,11 @@ class GD_CORE_API MultipleInstructionMetadata {
*/
MultipleInstructionMetadata &AddCodeOnlyParameter(
const gd::String &type, const gd::String &supplementaryInformation) {
#if defined(GD_IDE_ONLY)
if (expression)
expression->AddCodeOnlyParameter(type, supplementaryInformation);
if (condition)
condition->AddCodeOnlyParameter(type, supplementaryInformation);
if (action) action->AddCodeOnlyParameter(type, supplementaryInformation);
#endif
return *this;
}
@@ -73,11 +69,9 @@ class GD_CORE_API MultipleInstructionMetadata {
* \see gd::InstructionMetadata::SetDefaultValue
*/
MultipleInstructionMetadata &SetDefaultValue(const gd::String &defaultValue) {
#if defined(GD_IDE_ONLY)
if (expression) expression->SetDefaultValue(defaultValue);
if (condition) condition->SetDefaultValue(defaultValue);
if (action) action->SetDefaultValue(defaultValue);
#endif
return *this;
};
@@ -86,11 +80,9 @@ class GD_CORE_API MultipleInstructionMetadata {
*/
MultipleInstructionMetadata &SetParameterLongDescription(
const gd::String &longDescription) {
#if defined(GD_IDE_ONLY)
if (expression) expression->SetParameterLongDescription(longDescription);
if (condition) condition->SetParameterLongDescription(longDescription);
if (action) action->SetParameterLongDescription(longDescription);
#endif
return *this;
};
@@ -98,11 +90,9 @@ class GD_CORE_API MultipleInstructionMetadata {
* \see gd::InstructionMetadata::SetHidden
*/
MultipleInstructionMetadata &SetHidden() {
#if defined(GD_IDE_ONLY)
if (expression) expression->SetHidden();
if (condition) condition->SetHidden();
if (action) action->SetHidden();
#endif
return *this;
};
@@ -111,50 +101,40 @@ class GD_CORE_API MultipleInstructionMetadata {
* \see gd::InstructionMetadata::UseStandardRelationalOperatorParameters
*/
MultipleInstructionMetadata &UseStandardParameters(const gd::String &type) {
#if defined(GD_IDE_ONLY)
if (condition) condition->UseStandardRelationalOperatorParameters(type);
if (action) action->UseStandardOperatorParameters(type);
#endif
return *this;
}
MultipleInstructionMetadata &SetFunctionName(const gd::String &functionName) {
#if defined(GD_IDE_ONLY)
if (expression) expression->SetFunctionName(functionName);
if (condition) condition->SetFunctionName(functionName);
if (action) action->GetCodeExtraInformation().SetFunctionName(functionName);
#endif
return *this;
}
MultipleInstructionMetadata &SetGetter(const gd::String &getter) {
#if defined(GD_IDE_ONLY)
if (expression) expression->SetFunctionName(getter);
if (condition) condition->SetFunctionName(getter);
if (action) action->GetCodeExtraInformation().SetGetter(getter);
#endif
return *this;
}
MultipleInstructionMetadata &SetIncludeFile(const gd::String &includeFile) {
#if defined(GD_IDE_ONLY)
if (expression)
expression->GetCodeExtraInformation().SetIncludeFile(includeFile);
if (condition)
condition->GetCodeExtraInformation().SetIncludeFile(includeFile);
if (action) action->GetCodeExtraInformation().SetIncludeFile(includeFile);
#endif
return *this;
}
MultipleInstructionMetadata &AddIncludeFile(const gd::String &includeFile) {
#if defined(GD_IDE_ONLY)
if (expression)
expression->GetCodeExtraInformation().AddIncludeFile(includeFile);
if (condition)
condition->GetCodeExtraInformation().AddIncludeFile(includeFile);
if (action) action->GetCodeExtraInformation().AddIncludeFile(includeFile);
#endif
return *this;
}
@@ -162,10 +142,8 @@ class GD_CORE_API MultipleInstructionMetadata {
* \see gd::InstructionMetadata::MarkAsSimple
*/
MultipleInstructionMetadata &MarkAsSimple() {
#if defined(GD_IDE_ONLY)
if (condition) condition->MarkAsSimple();
if (action) action->MarkAsSimple();
#endif
return *this;
}
@@ -173,10 +151,8 @@ class GD_CORE_API MultipleInstructionMetadata {
* \see gd::InstructionMetadata::MarkAsAdvanced
*/
MultipleInstructionMetadata &MarkAsAdvanced() {
#if defined(GD_IDE_ONLY)
if (condition) condition->MarkAsAdvanced();
if (action) action->MarkAsAdvanced();
#endif
return *this;
}
@@ -184,10 +160,8 @@ class GD_CORE_API MultipleInstructionMetadata {
* \see gd::InstructionMetadata::MarkAsComplex
*/
MultipleInstructionMetadata &MarkAsComplex() {
#if defined(GD_IDE_ONLY)
if (condition) condition->MarkAsComplex();
if (action) action->MarkAsComplex();
#endif
return *this;
}

View File

@@ -194,7 +194,8 @@ class GD_CORE_API ParameterMetadata {
parameterType == "objectEffectName" ||
parameterType == "objectEffectParameterName" ||
parameterType == "objectPointName" ||
parameterType == "objectAnimationName";
parameterType == "objectAnimationName" ||
parameterType == "functionParameterName";
} else if (type == "variable") {
return parameterType == "objectvar" || parameterType == "globalvar" ||
parameterType == "scenevar";

View File

@@ -19,4 +19,34 @@ class GD_CORE_API EventsListUnfolder {
}
}
}
static void FoldAll(gd::EventsList& list) {
for (size_t i = 0; i < list.size(); ++i) {
gd::BaseEvent& event = list[i];
event.SetFolded(true);
if (event.CanHaveSubEvents() && event.GetSubEvents().size() > 0) {
FoldAll(event.GetSubEvents());
}
}
}
/**
* \brief Recursively unfold all the events until a certain level of depth.
* 0 is the top level. If you want to unfold all events regardless of its depth,
* use `maxLevel = -1`. `currentLevel` is used for the recursion.
*/
static void UnfoldToLevel(gd::EventsList& list,
const int8_t maxLevel,
const std::size_t currentLevel = 0) {
if (maxLevel >= 0 && currentLevel > maxLevel) return;
for (size_t i = 0; i < list.size(); ++i) {
gd::BaseEvent& event = list[i];
event.SetFolded(false);
if (event.CanHaveSubEvents() && event.GetSubEvents().size() > 0 &&
(maxLevel == -1 || currentLevel <= maxLevel)) {
UnfoldToLevel(event.GetSubEvents(), maxLevel, currentLevel + 1);
}
}
}
};

View File

@@ -19,11 +19,14 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
using namespace std;
namespace gd {
const gd::String EventsRefactorer::searchIgnoredCharacters = ";:,#()";
/**
* \brief Go through the nodes and change the given object name to a new one.
*
@@ -675,16 +678,27 @@ bool EventsRefactorer::ReplaceStringInConditions(
}
vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
const gd::Platform& platform,
gd::EventsList& events,
gd::String search,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventStrings) {
bool inEventStrings,
bool inEventSentences) {
vector<EventsSearchResult> results;
const gd::String& ignored_characters = EventsRefactorer::searchIgnoredCharacters;
search.replace_if(search.begin(),
search.end(),
[ignored_characters](const char &c) {
return ignored_characters.find(c) != gd::String::npos;
},
"");
search = search.LeftTrim().RightTrim();
search.RemoveConsecutiveOccurrences(search.begin(), search.end(), ' ');
for (std::size_t i = 0; i < events.size(); ++i) {
bool eventAddedInResults = false;
@@ -694,7 +708,7 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
if (!eventAddedInResults &&
SearchStringInConditions(
project, layout, *conditionsVectors[j], search, matchCase)) {
platform, *conditionsVectors[j], search, matchCase, inEventSentences)) {
results.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
@@ -709,7 +723,7 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
if (!eventAddedInResults &&
SearchStringInActions(
project, layout, *actionsVectors[j], search, matchCase)) {
platform, *actionsVectors[j], search, matchCase, inEventSentences)) {
results.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
@@ -720,7 +734,7 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
if (inEventStrings) {
if (!eventAddedInResults &&
SearchStringInEvent(project, layout, events[i], search, matchCase)) {
SearchStringInEvent(events[i], search, matchCase)) {
results.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
@@ -730,14 +744,14 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
if (events[i].CanHaveSubEvents()) {
vector<EventsSearchResult> subResults =
SearchInEvents(project,
layout,
SearchInEvents(platform,
events[i].GetSubEvents(),
search,
matchCase,
inConditions,
inActions,
inEventStrings);
inEventStrings,
inEventSentences);
std::copy(
subResults.begin(), subResults.end(), std::back_inserter(results));
}
@@ -746,11 +760,12 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
return results;
}
bool EventsRefactorer::SearchStringInActions(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::InstructionsList& actions,
gd::String search,
bool matchCase) {
bool EventsRefactorer::SearchStringInActions(
const gd::Platform& platform,
gd::InstructionsList& actions,
gd::String search,
bool matchCase,
bool inSentences) {
for (std::size_t aId = 0; aId < actions.size(); ++aId) {
for (std::size_t pNb = 0; pNb < actions[aId].GetParameters().size();
++pNb) {
@@ -765,24 +780,60 @@ bool EventsRefactorer::SearchStringInActions(gd::ObjectsContainer& project,
if (foundPosition != gd::String::npos) return true;
}
if (inSentences && SearchStringInFormattedText(
platform, actions[aId], search, matchCase, false))
return true;
if (!actions[aId].GetSubInstructions().empty() &&
SearchStringInActions(project,
layout,
SearchStringInActions(platform,
actions[aId].GetSubInstructions(),
search,
matchCase))
matchCase,
inSentences))
return true;
}
return false;
}
bool EventsRefactorer::SearchStringInFormattedText(
const gd::Platform& platform,
gd::Instruction& instruction,
gd::String search,
bool matchCase,
bool isCondition) {
const auto& metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
gd::String completeSentence = gd::InstructionSentenceFormatter::Get()->GetFullText(instruction, metadata);
const gd::String& ignored_characters = EventsRefactorer::searchIgnoredCharacters;
completeSentence.replace_if(completeSentence.begin(),
completeSentence.end(),
[ignored_characters](const char &c) {
return ignored_characters.find(c) != gd::String::npos;
},
"");
completeSentence.RemoveConsecutiveOccurrences(
completeSentence.begin(), completeSentence.end(), ' ');
size_t foundPosition = matchCase
? completeSentence.find(search)
: completeSentence.FindCaseInsensitive(search);
return foundPosition != gd::String::npos;
}
bool EventsRefactorer::SearchStringInConditions(
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
const gd::Platform& platform,
gd::InstructionsList& conditions,
gd::String search,
bool matchCase) {
bool matchCase,
bool inSentences) {
for (std::size_t cId = 0; cId < conditions.size(); ++cId) {
for (std::size_t pNb = 0; pNb < conditions[cId].GetParameters().size();
++pNb) {
@@ -797,21 +848,23 @@ bool EventsRefactorer::SearchStringInConditions(
if (foundPosition != gd::String::npos) return true;
}
if (inSentences && SearchStringInFormattedText(
platform, conditions[cId], search, matchCase, true))
return true;
if (!conditions[cId].GetSubInstructions().empty() &&
SearchStringInConditions(project,
layout,
SearchStringInConditions(platform,
conditions[cId].GetSubInstructions(),
search,
matchCase))
matchCase,
inSentences))
return true;
}
return false;
}
bool EventsRefactorer::SearchStringInEvent(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::BaseEvent& event,
bool EventsRefactorer::SearchStringInEvent(gd::BaseEvent& event,
gd::String search,
bool matchCase) {
for (gd::String str : event.GetAllSearchableStrings()) {

View File

@@ -41,7 +41,7 @@ class GD_CORE_API EventsSearchResult {
std::size_t positionInList;
bool IsEventsListValid() const { return eventsList != nullptr; }
/**
* \brief Get the events list containing the event pointed by the EventsSearchResult.
* \warning Only call this when IsEventsListValid returns true.
@@ -49,7 +49,7 @@ class GD_CORE_API EventsSearchResult {
const gd::EventsList & GetEventsList() const { return *eventsList; }
std::size_t GetPositionInList() const { return positionInList; }
bool IsEventValid() const { return !event.expired(); }
/**
@@ -72,7 +72,7 @@ class GD_CORE_API EventsSearchResult {
class GD_CORE_API EventsRefactorer {
public:
/**
* Replace all occurences of an object name by another name
* Replace all occurrences of an object name by another name
* ( include : objects in parameters and in math/text expressions of all
* events ).
*/
@@ -98,14 +98,14 @@ class GD_CORE_API EventsRefactorer {
* \return A vector containing EventsSearchResult objects filled with events
* containing the string
*/
static std::vector<EventsSearchResult> SearchInEvents(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
static std::vector<EventsSearchResult> SearchInEvents(const gd::Platform& platform,
gd::EventsList& events,
gd::String search,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventStrings);
bool inEventStrings,
bool inEventSentences);
/**
* Replace all occurrences of a gd::String in events
@@ -123,7 +123,7 @@ class GD_CORE_API EventsRefactorer {
private:
/**
* Replace all occurences of an object name by another name in an action
* Replace all occurrences of an object name by another name in an action
* ( include : objects in parameters and in math/text expressions ).
*
* \return true if something was modified.
@@ -136,7 +136,7 @@ class GD_CORE_API EventsRefactorer {
gd::String newName);
/**
* Replace all occurences of an object name by another name in a condition
* Replace all occurrences of an object name by another name in a condition
* ( include : objects in parameters and in math/text expressions ).
*
* \return true if something was modified.
@@ -185,7 +185,7 @@ class GD_CORE_API EventsRefactorer {
gd::String name);
/**
* Replace all occurences of a gd::String in conditions
* Replace all occurrences of a gd::String in conditions
*
* \return true if something was modified.
*/
@@ -197,7 +197,7 @@ class GD_CORE_API EventsRefactorer {
bool matchCase);
/**
* Replace all occurences of a gd::String in actions
* Replace all occurrences of a gd::String in actions
*
* \return true if something was modified.
*/
@@ -208,21 +208,26 @@ class GD_CORE_API EventsRefactorer {
gd::String newString,
bool matchCase);
static bool SearchStringInActions(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
static bool SearchStringInFormattedText(const gd::Platform& platform,
gd::Instruction& instruction,
gd::String search,
bool matchCase,
bool isCondition);
static bool SearchStringInActions(const gd::Platform& platform,
gd::InstructionsList& actions,
gd::String search,
bool matchCase);
static bool SearchStringInConditions(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
bool matchCase,
bool inSentences);
static bool SearchStringInConditions(const gd::Platform& platform,
gd::InstructionsList& conditions,
gd::String search,
bool matchCase);
static bool SearchStringInEvent(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::BaseEvent& events,
gd::String search,
bool matchCase);
bool matchCase,
bool inSentences);
static bool SearchStringInEvent(gd::BaseEvent& events,
gd::String search,
bool matchCase);
static const gd::String searchIgnoredCharacters;
EventsRefactorer(){};
};

View File

@@ -9,12 +9,12 @@
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
namespace gd {
class Expression;
@@ -32,7 +32,7 @@ namespace gd {
* The IDE is responsible for actually *searching* and showing the completions -
* this is only describing what must be listed.
*/
struct ExpressionCompletionDescription {
struct GD_CORE_API ExpressionCompletionDescription {
public:
/**
* The different kind of completions that can be described.
@@ -274,7 +274,7 @@ struct ExpressionCompletionDescription {
/**
* \brief Turn an ExpressionCompletionDescription to a string.
*/
std::ostream& operator<<(std::ostream& os,
GD_CORE_API std::ostream& operator<<(std::ostream& os,
ExpressionCompletionDescription const& value);
/**
@@ -326,10 +326,7 @@ class GD_CORE_API ExpressionCompletionFinder
node.type, "", searchedPosition + 1, searchedPosition + 1));
}
void OnVisitOperatorNode(OperatorNode& node) override {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, "", searchedPosition + 1, searchedPosition + 1));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, "", searchedPosition + 1, searchedPosition + 1));
// No completions.
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
completions.push_back(ExpressionCompletionDescription::ForObject(
@@ -359,8 +356,9 @@ class GD_CORE_API ExpressionCompletionFinder
}
// Search the parameter metadata index skipping invisible ones.
size_t visibleParameterIndex = 0;
size_t metadataParameterIndex = ExpressionParser2::WrittenParametersFirstIndex(
functionCall->objectName, functionCall->behaviorName);
size_t metadataParameterIndex =
ExpressionParser2::WrittenParametersFirstIndex(
functionCall->objectName, functionCall->behaviorName);
const gd::ParameterMetadata* parameterMetadata = nullptr;
while (metadataParameterIndex <

View File

@@ -4,7 +4,6 @@
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
#include <algorithm>
#include <iostream>
@@ -90,6 +89,19 @@ InstructionSentenceFormatter::GetAsFormattedText(
return formattedStr;
}
} // namespace gd
gd::String InstructionSentenceFormatter::GetFullText(
const gd::Instruction &instr, const gd::InstructionMetadata &metadata)
{
const std::vector<std::pair<gd::String, gd::TextFormatting> > formattedText =
GetAsFormattedText(instr, metadata);
#endif
gd::String completeSentence = "";
for (std::size_t id = 0; id < formattedText.size(); ++id) {
completeSentence += formattedText.at(id).first;
}
return completeSentence;
}
} // namespace gd

View File

@@ -4,7 +4,6 @@
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef TRANSLATEACTION_H
#define TRANSLATEACTION_H
#include <map>
@@ -39,6 +38,9 @@ class GD_CORE_API InstructionSentenceFormatter {
return (static_cast<InstructionSentenceFormatter *>(_singleton));
}
gd::String GetFullText(const gd::Instruction &instr,
const gd::InstructionMetadata &metadata);
static void DestroySingleton() {
if (NULL != _singleton) {
delete _singleton;
@@ -55,4 +57,3 @@ class GD_CORE_API InstructionSentenceFormatter {
} // namespace gd
#endif // TRANSLATEACTION_H
#endif

View File

@@ -30,6 +30,16 @@ void ArbitraryResourceWorker::ExposeImage(gd::String& imageName){
// do.
};
void ArbitraryResourceWorker::ExposeJson(gd::String& jsonName){
// Nothing to do by default - each child class can define here the action to
// do.
};
void ArbitraryResourceWorker::ExposeVideo(gd::String& videoName){
// Nothing to do by default - each child class can define here the action to
// do.
};
void ArbitraryResourceWorker::ExposeBitmapFont(gd::String& bitmapFontName){
// Nothing to do by default - each child class can define here the action to
// do.

View File

@@ -70,6 +70,16 @@ class GD_CORE_API ArbitraryResourceWorker {
*/
virtual void ExposeFont(gd::String &fontName);
/**
* \brief Expose a JSON, which is always a reference to a "json" resource.
*/
virtual void ExposeJson(gd::String &jsonName);
/**
* \brief Expose a video, which is always a reference to a "video" resource.
*/
virtual void ExposeVideo(gd::String &videoName);
/**
* \brief Expose a bitmap font, which is always a reference to a "bitmapFont" resource.
*/

View File

@@ -10,6 +10,7 @@
#include <set>
#include <vector>
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
@@ -36,17 +37,20 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
virtual ~ResourcesInUseHelper(){};
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
std::set<gd::String>& GetAllJsons() { return GetAll("json"); };
std::set<gd::String>& GetAllVideos() { return GetAll("video"); };
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
std::set<gd::String>& GetAll(const gd::String& resourceType) {
return resourceType == "image"
? allImages
: (resourceType == "audio"
? allAudios
: (resourceType == "font")
? allFonts
: (resourceType == "bitmapFont") ? allBitmapFonts : emptyResources);
if (resourceType == "image") return allImages;
if (resourceType == "audio") return allAudios;
if (resourceType == "font") return allFonts;
if (resourceType == "json") return allJsons;
if (resourceType == "video") return allVideos;
if (resourceType == "bitmapFont") return allBitmapFonts;
return emptyResources;
};
virtual void ExposeFile(gd::String& resource) override{
@@ -61,6 +65,12 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
virtual void ExposeFont(gd::String& fontResourceName) override {
allFonts.insert(fontResourceName);
};
virtual void ExposeJson(gd::String& jsonResourceName) override {
allJsons.insert(jsonResourceName);
};
virtual void ExposeVideo(gd::String& videoResourceName) override {
allVideos.insert(videoResourceName);
};
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
allBitmapFonts.insert(bitmapFontResourceName);
};
@@ -69,6 +79,8 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
std::set<gd::String> allImages;
std::set<gd::String> allAudios;
std::set<gd::String> allFonts;
std::set<gd::String> allJsons;
std::set<gd::String> allVideos;
std::set<gd::String> allBitmapFonts;
std::set<gd::String> emptyResources;
};

View File

@@ -46,6 +46,12 @@ class ResourcesRenamer : public gd::ArbitraryResourceWorker {
virtual void ExposeFont(gd::String& fontResourceName) override {
RenameIfNeeded(fontResourceName);
};
virtual void ExposeJson(gd::String& jsonResourceName) override {
RenameIfNeeded(jsonResourceName);
};
virtual void ExposeVideo(gd::String& videoResourceName) override {
RenameIfNeeded(videoResourceName);
};
virtual void ExposeBitmapFont(gd::String& bitmapFontName) override {
RenameIfNeeded(bitmapFontName);
};

View File

@@ -17,6 +17,7 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
element.SetAttribute("fullName", fullName);
element.SetAttribute("description", description);
element.SetAttribute("sentence", sentence);
element.SetAttribute("group", group);
element.SetBoolAttribute("private", isPrivate);
events.SerializeTo(element.AddChild("events"));
@@ -44,6 +45,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
fullName = element.GetStringAttribute("fullName");
description = element.GetStringAttribute("description");
sentence = element.GetStringAttribute("sentence");
group = element.GetStringAttribute("group");
isPrivate = element.GetBoolAttribute("private");
events.UnserializeFrom(project, element.GetChild("events"));

View File

@@ -102,6 +102,19 @@ class GD_CORE_API EventsFunction {
return *this;
}
/**
* \brief Get the group of the instruction in the editor.
*/
const gd::String& GetGroup() const { return group; };
/**
* \brief Set the group of the instruction in the editor.
*/
EventsFunction& SetGroup(const gd::String& group_) {
group = group_;
return *this;
}
enum FunctionType { Action, Condition, Expression, StringExpression };
/**
@@ -188,6 +201,7 @@ class GD_CORE_API EventsFunction {
gd::String fullName;
gd::String description;
gd::String sentence;
gd::String group;
gd::EventsList events;
FunctionType functionType;
std::vector<gd::ParameterMetadata> parameters;

View File

@@ -3,7 +3,6 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include "EventsFunctionsExtension.h"
#include "EventsBasedBehavior.h"
@@ -51,6 +50,11 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
element.SetAttribute("description", description);
element.SetAttribute("name", name);
element.SetAttribute("fullName", fullName);
if (!originName.empty() || !originIdentifier.empty()) {
element.AddChild("origin")
.SetAttribute("name", originName)
.SetAttribute("identifier", originIdentifier);
}
auto& tagsElement = element.AddChild("tags");
tagsElement.ConsiderAsArray();
for (const auto& tag : tags) {
@@ -88,6 +92,14 @@ void EventsFunctionsExtension::UnserializeFrom(
iconUrl = element.GetStringAttribute("iconUrl");
helpPath = element.GetStringAttribute("helpPath");
if (element.HasChild("origin")) {
gd::String originName =
element.GetChild("origin").GetStringAttribute("name", "");
gd::String originIdentifier =
element.GetChild("origin").GetStringAttribute("identifier", "");
SetOrigin(originName, originIdentifier);
}
tags.clear();
auto& tagsElement = element.GetChild("tags");
if (!tagsElement.IsValueUndefined()) {
@@ -138,5 +150,3 @@ bool EventsFunctionsExtension::IsExtensionLifecycleEventsFunction(
}
} // namespace gd
#endif

View File

@@ -3,7 +3,6 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef GDCORE_EVENTSFUNCTIONEXTENSION_H
#define GDCORE_EVENTSFUNCTIONEXTENSION_H
@@ -140,6 +139,23 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
return eventsBasedBehaviors;
}
/**
* \brief Sets an extension origin. This method is not present since the
* beginning so the projects created before that will have extensions
* installed from the store without an origin. Keep that in mind when creating
* features that rely on an extension's origin.
*/
virtual void SetOrigin(const gd::String& originName_,
const gd::String& originIdentifier_) {
originName = originName_;
originIdentifier = originIdentifier_;
}
virtual const gd::String& GetOriginName() const { return originName; }
virtual const gd::String& GetOriginIdentifier() const {
return originIdentifier;
}
/** \name Dependencies
*/
///@{
@@ -226,6 +242,8 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
std::vector<gd::String> authorIds;
gd::String author;
gd::String previewIconUrl;
gd::String originName;
gd::String originIdentifier;
gd::String iconUrl;
gd::String helpPath; ///< The relative path to the help for this extension in
///< the documentation (or an absolute URL).
@@ -236,4 +254,3 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
} // namespace gd
#endif // GDCORE_EVENTSFUNCTIONEXTENSION_H
#endif

View File

@@ -1,5 +1,5 @@
#if defined(GD_IDE_ONLY)
#include "GDCore/Project/ExternalEvents.h"
#include "ExternalEvents.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Serialization.h"
@@ -48,4 +48,3 @@ void ExternalEvents::UnserializeFrom(gd::Project& project,
}
} // namespace gd
#endif

View File

@@ -3,12 +3,12 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef GDCORE_EXTERNALEVENTS_H
#define GDCORE_EXTERNALEVENTS_H
#include <ctime>
#include <memory>
#include <vector>
#include "GDCore/Events/EventsList.h"
#include "GDCore/String.h"
namespace gd {
@@ -135,4 +135,3 @@ struct ExternalEventsHasName
} // namespace gd
#endif // GDCORE_EXTERNALEVENTS_H
#endif

View File

@@ -5,6 +5,7 @@
*/
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -15,19 +16,15 @@ namespace gd {
void ExternalLayout::UnserializeFrom(const SerializerElement& element) {
name = element.GetStringAttribute("name", "", "Name");
instances.UnserializeFrom(element.GetChild("instances", 0, "Instances"));
#if defined(GD_IDE_ONLY)
editorSettings.UnserializeFrom(element.GetChild("editionSettings"));
#endif
associatedLayout = element.GetStringAttribute("associatedLayout");
}
#if defined(GD_IDE_ONLY)
void ExternalLayout::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", name);
instances.SerializeTo(element.AddChild("instances"));
editorSettings.SerializeTo(element.AddChild("editionSettings"));
element.SetAttribute("associatedLayout", associatedLayout);
}
#endif
} // namespace gd

View File

@@ -7,14 +7,13 @@
#ifndef GDCORE_EXTERNALLAYOUT_H
#define GDCORE_EXTERNALLAYOUT_H
#include <memory>
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/String.h"
namespace gd {
class SerializerElement;
}
#if defined(GD_IDE_ONLY)
#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h"
#endif
namespace gd {
@@ -54,7 +53,6 @@ class GD_CORE_API ExternalLayout {
*/
gd::InitialInstancesContainer& GetInitialInstances() { return instances; }
#if defined(GD_IDE_ONLY)
/**
* \brief Get the user settings for the IDE.
*/
@@ -65,10 +63,7 @@ class GD_CORE_API ExternalLayout {
/**
* \brief Get the user settings for the IDE.
*/
gd::EditorSettings& GetAssociatedEditorSettings() {
return editorSettings;
}
#endif
gd::EditorSettings& GetAssociatedEditorSettings() { return editorSettings; }
/**
* \brief Get the name of the layout last used to edit the external layout.
@@ -80,15 +75,13 @@ class GD_CORE_API ExternalLayout {
*/
void SetAssociatedLayout(const gd::String& name) { associatedLayout = name; }
/** \name Serialization
*/
///@{
#if defined(GD_IDE_ONLY)
/** \name Serialization
*/
///@{
/**
* \brief Serialize external layout.
*/
void SerializeTo(SerializerElement& element) const;
#endif
/**
* \brief Unserialize the external layout.
@@ -99,9 +92,7 @@ class GD_CORE_API ExternalLayout {
private:
gd::String name;
gd::InitialInstancesContainer instances;
#if defined(GD_IDE_ONLY)
gd::EditorSettings editorSettings;
#endif
gd::String associatedLayout;
};

View File

@@ -19,6 +19,7 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
element.AddChild("type").SetStringValue(type);
element.AddChild("label").SetStringValue(label);
element.AddChild("description").SetStringValue(description);
element.AddChild("group").SetStringValue(group);
SerializerElement& extraInformationElement =
element.AddChild("extraInformation");
extraInformationElement.ConsiderAsArray();
@@ -33,6 +34,7 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
type = element.GetChild("type").GetStringValue();
label = element.GetChild("label").GetStringValue();
description = element.GetChild("description").GetStringValue();
group = element.GetChild("group").GetStringValue();
extraInformation.clear();
const SerializerElement& extraInformationElement =

View File

@@ -76,6 +76,14 @@ class GD_CORE_API PropertyDescriptor {
return *this;
}
/**
* \brief Change the group where this property is displayed to the user, if any.
*/
PropertyDescriptor& SetGroup(gd::String group_) {
group = group_;
return *this;
}
/**
* \brief Set and replace the additional information for the property.
*/
@@ -100,6 +108,7 @@ class GD_CORE_API PropertyDescriptor {
const gd::String& GetType() const { return type; }
const gd::String& GetLabel() const { return label; }
const gd::String& GetDescription() const { return description; }
const gd::String& GetGroup() const { return group; }
const std::vector<gd::String>& GetExtraInfo() const {
return extraInformation;
@@ -153,6 +162,7 @@ class GD_CORE_API PropertyDescriptor {
///< the class responsible for updating the property grid.
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<gd::String>
extraInformation; ///< Can be used to store for example the available
///< choices, if a property is a displayed as a combo

View File

@@ -24,12 +24,9 @@ gd::String Resource::badStr;
Resource ResourcesManager::badResource;
gd::String ResourcesManager::badResourceName;
#if defined(GD_IDE_ONLY)
ResourceFolder ResourcesManager::badFolder;
Resource ResourceFolder::badResource;
#endif
#if defined(GD_IDE_ONLY)
void ResourceFolder::Init(const ResourceFolder& other) {
name = other.name;
@@ -38,19 +35,16 @@ void ResourceFolder::Init(const ResourceFolder& other) {
resources.push_back(std::shared_ptr<Resource>(other.resources[i]->Clone()));
}
}
#endif
void ResourcesManager::Init(const ResourcesManager& other) {
resources.clear();
for (std::size_t i = 0; i < other.resources.size(); ++i) {
resources.push_back(std::shared_ptr<Resource>(other.resources[i]->Clone()));
}
#if defined(GD_IDE_ONLY)
folders.clear();
for (std::size_t i = 0; i < other.folders.size(); ++i) {
folders.push_back(other.folders[i]);
}
#endif
}
Resource& ResourcesManager::GetResource(const gd::String& name) {
@@ -147,7 +141,6 @@ std::vector<gd::String> ResourcesManager::FindFilesNotInResources(
return filesNotInResources;
}
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> Resource::GetProperties() const {
std::map<gd::String, gd::PropertyDescriptor> nothing;
return nothing;
@@ -443,9 +436,7 @@ void ResourcesManager::RemoveResource(const gd::String& name) {
for (std::size_t i = 0; i < folders.size(); ++i)
folders[i].RemoveResource(name);
}
#endif
#if defined(GD_IDE_ONLY)
void ResourceFolder::UnserializeFrom(const SerializerElement& element,
gd::ResourcesManager& parentManager) {
name = element.GetStringAttribute("name");
@@ -470,7 +461,6 @@ void ResourceFolder::SerializeTo(SerializerElement& element) const {
.SetAttribute("name", resources[i]->GetName());
}
}
#endif
void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
resources.clear();
@@ -500,7 +490,6 @@ void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
resources.push_back(resource);
}
#if defined(GD_IDE_ONLY)
folders.clear();
const SerializerElement& resourcesFoldersElement =
element.GetChild("resourceFolders", 0, "ResourceFolders");
@@ -511,10 +500,8 @@ void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
folders.push_back(folder);
}
#endif
}
#if defined(GD_IDE_ONLY)
void ResourcesManager::SerializeTo(SerializerElement& element) const {
SerializerElement& resourcesElement = element.AddChild("resources");
resourcesElement.ConsiderAsArrayOf("resource");
@@ -543,7 +530,6 @@ void ResourcesManager::SerializeTo(SerializerElement& element) const {
for (std::size_t i = 0; i < folders.size(); ++i)
folders[i].SerializeTo(resourcesFoldersElement.AddChild("folder"));
}
#endif
void ImageResource::SetFile(const gd::String& newFile) {
file = newFile;
@@ -560,14 +546,12 @@ void ImageResource::UnserializeFrom(const SerializerElement& element) {
SetFile(element.GetStringAttribute("file"));
}
#if defined(GD_IDE_ONLY)
void ImageResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("alwaysLoaded", alwaysLoaded);
element.SetAttribute("smoothed", smooth);
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute("file", GetFile());
}
#endif
void AudioResource::SetFile(const gd::String& newFile) {
file = newFile;
@@ -584,14 +568,12 @@ void AudioResource::UnserializeFrom(const SerializerElement& element) {
SetPreloadAsSound(element.GetBoolAttribute("preloadAsSound"));
}
#if defined(GD_IDE_ONLY)
void AudioResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute("file", GetFile());
element.SetAttribute("preloadAsMusic", PreloadAsMusic());
element.SetAttribute("preloadAsSound", PreloadAsSound());
}
#endif
void FontResource::SetFile(const gd::String& newFile) {
file = newFile;
@@ -606,12 +588,10 @@ void FontResource::UnserializeFrom(const SerializerElement& element) {
SetFile(element.GetStringAttribute("file"));
}
#if defined(GD_IDE_ONLY)
void FontResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute("file", GetFile());
}
#endif
void VideoResource::SetFile(const gd::String& newFile) {
file = newFile;
@@ -626,12 +606,10 @@ void VideoResource::UnserializeFrom(const SerializerElement& element) {
SetFile(element.GetStringAttribute("file"));
}
#if defined(GD_IDE_ONLY)
void VideoResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute("file", GetFile());
}
#endif
void JsonResource::SetFile(const gd::String& newFile) {
file = newFile;
@@ -647,7 +625,6 @@ void JsonResource::UnserializeFrom(const SerializerElement& element) {
DisablePreload(element.GetBoolAttribute("disablePreload", false));
}
#if defined(GD_IDE_ONLY)
void JsonResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute("file", GetFile());
@@ -672,7 +649,6 @@ bool JsonResource::UpdateProperty(const gd::String& name,
return true;
}
#endif
void BitmapFontResource::SetFile(const gd::String& newFile) {
file = newFile;
@@ -687,14 +663,11 @@ void BitmapFontResource::UnserializeFrom(const SerializerElement& element) {
SetFile(element.GetStringAttribute("file"));
}
#if defined(GD_IDE_ONLY)
void BitmapFontResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("userAdded", IsUserAdded());
element.SetAttribute("file", GetFile());
}
#endif
#if defined(GD_IDE_ONLY)
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
@@ -702,7 +675,6 @@ ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
return *this;
}
#endif
ResourcesManager::ResourcesManager(const ResourcesManager& other) {
Init(other);

View File

@@ -104,7 +104,6 @@ class GD_CORE_API Resource {
*/
virtual const gd::String& GetMetadata() const { return metadata; }
#if defined(GD_IDE_ONLY)
/** \name Resources properties
* Reading and updating resources properties
*/
@@ -136,7 +135,6 @@ class GD_CORE_API Resource {
return false;
};
///@}
#endif
/**
* \brief Serialize the object
@@ -186,7 +184,6 @@ class GD_CORE_API ImageResource : public Resource {
*/
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
@@ -196,7 +193,6 @@ class GD_CORE_API ImageResource : public Resource {
* \brief Serialize the object
*/
void SerializeTo(SerializerElement& element) const override;
#endif
/**
* \brief Unserialize the objectt.
@@ -238,14 +234,12 @@ class GD_CORE_API AudioResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
void SerializeTo(SerializerElement& element) const override;
#endif
void UnserializeFrom(const SerializerElement& element) override;
@@ -292,10 +286,8 @@ class GD_CORE_API FontResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
void SerializeTo(SerializerElement& element) const override;
#endif
void UnserializeFrom(const SerializerElement& element) override;
@@ -320,10 +312,8 @@ class GD_CORE_API VideoResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
void SerializeTo(SerializerElement& element) const override;
#endif
void UnserializeFrom(const SerializerElement& element) override;
@@ -348,14 +338,12 @@ class GD_CORE_API JsonResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
void SerializeTo(SerializerElement& element) const override;
#endif
void UnserializeFrom(const SerializerElement& element) override;
@@ -391,10 +379,8 @@ class GD_CORE_API BitmapFontResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
#if defined(GD_IDE_ONLY)
virtual bool UseFile() override { return true; }
void SerializeTo(SerializerElement& element) const override;
#endif
void UnserializeFrom(const SerializerElement& element) override;
@@ -463,7 +449,6 @@ class GD_CORE_API ResourcesManager {
*/
std::vector<gd::String> FindFilesNotInResources(const std::vector<gd::String>& filesToCheck) const;
#if defined(GD_IDE_ONLY)
/**
* \brief Return a (smart) pointer to a resource.
*/
@@ -557,7 +542,6 @@ class GD_CORE_API ResourcesManager {
* \brief Serialize the object
*/
void SerializeTo(SerializerElement& element) const;
#endif
/**
* \brief Unserialize the objectt.
@@ -568,18 +552,13 @@ class GD_CORE_API ResourcesManager {
void Init(const ResourcesManager& other);
std::vector<std::shared_ptr<Resource> > resources;
#if defined(GD_IDE_ONLY)
std::vector<ResourceFolder> folders;
#endif
#if defined(GD_IDE_ONLY)
static ResourceFolder badFolder;
#endif
static Resource badResource;
static gd::String badResourceName;
};
#if defined(GD_IDE_ONLY)
class GD_CORE_API ResourceFolder {
public:
ResourceFolder(){};
@@ -654,7 +633,6 @@ class GD_CORE_API ResourceFolder {
void Init(const ResourceFolder& other);
static Resource badResource;
};
#endif
} // namespace gd

View File

@@ -6,6 +6,7 @@
#include "GDCore/String.h"
#include <algorithm>
#include <string.h>
#include <SFML/System/String.hpp>
@@ -283,6 +284,42 @@ String& String::insert( size_type pos, const String &str )
return *this;
}
String& String::replace_if(iterator i1, iterator i2, std::function<bool(char32_t)> p, const String &str)
{
String::size_type offset = 1;
iterator it = i1.base();
while(it < i2.base())
{
if (p(*it)) { replace(std::distance(begin(), it), offset, str); }
else { it++; }
}
return *this;
}
String& String::RemoveConsecutiveOccurrences(iterator i1, iterator i2, const char c)
{
std::vector<std::pair<size_type, size_type>> ranges_to_remove;
for(iterator current_index = i1.base(); current_index < i2.base(); current_index++)
{
if (*current_index == c){
iterator current_subindex = current_index;
std::advance(current_subindex, 1);
if (*current_subindex == c) {
while(current_subindex < end() && *current_subindex == c)
{
current_subindex++;
}
replace(std::distance(begin(), current_index),
std::distance(current_index, current_subindex),
c);
std::advance(current_index, 1);
}
}
}
return *this;
}
String& String::replace( iterator i1, iterator i2, const String &str )
{
m_string.replace(i1.base(), i2.base(), str.m_string);
@@ -290,6 +327,31 @@ String& String::replace( iterator i1, iterator i2, const String &str )
return *this;
}
String& String::replace( iterator i1, iterator i2, size_type n, const char c )
{
m_string.replace(i1.base(), i2.base(), n, c);
return *this;
}
String& String::replace( String::size_type pos, String::size_type len, const char c )
{
if(pos > size())
throw std::out_of_range("[gd::String::replace] starting pos greater than size");
iterator i1 = begin();
std::advance( i1, pos );
iterator i2 = i1;
while(i2 != end() && len > 0) //Increment "len" times and stop if end() is reached
{
++i2;
--len;
}
return replace( i1, i2, 1, c );
}
String& String::replace( String::size_type pos, String::size_type len, const String &str )
{
if(pos > size())

View File

@@ -438,15 +438,52 @@ public:
*/
String& replace( iterator i1, iterator i2, const String &str );
/**
* \brief Replace the portion of the String between **i1** and **i2** (**i2** not
* included) by **n** consecutive copies of character **c**.
* \return *this
*
* **Iterators :** All iterators may be invalidated.
*/
String& replace( iterator i1, iterator i2, size_type n, const char c );
/**
* \brief Replace the portion of the String between **pos** and **pos** + **len**
* (the character at **pos** + **len** is not included)
* (the character at **pos** + **len** is not included) with **str**.
* \return *this
*
* **Iterators :** All iterators may be invalidated.
*/
String& replace( size_type pos, size_type len, const String &str );
/**
* \brief Replace the portion of the String between **pos** and **pos** + **len**
* (the character at **pos** + **len** is not included) with the character **c**.
* \return *this
*
* **Iterators :** All iterators may be invalidated.
*/
String& replace( size_type pos, size_type len, const char c );
/**
* \brief Search in the portion of the String between **i1** and **i2** (**i2** not
* included) for characters matching predicate function **p** and replace them
* by the String **str**.
* \return *this
*
* **Iterators :** All iterators may be invalidated.
*/
String& replace_if( iterator i1, iterator i2, std::function<bool(char32_t)> p, const String &str );
/**
* \brief Remove consecutive occurrences of the character **c** in the portion of the
* between **i1** and **i2** (**i2** not included) to replace it by a single occurrence.
* \return *this
*
* **Iterators :** All iterators may be invalidated.
*/
String& RemoveConsecutiveOccurrences(iterator i1, iterator i2, const char c);
/**
* \brief Erase the characters between **first** and **last** (**last** not included).
* \param first an iterator to the first character to remove

View File

@@ -87,42 +87,27 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
}
}
SECTION("Operator (number)") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions1{
gd::ExpressionCompletionDescription::ForObject("number", "", 1, 1),
gd::ExpressionCompletionDescription::ForExpression("number", "", 1, 1)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions2{
gd::ExpressionCompletionDescription::ForObject("number", "", 2, 2),
gd::ExpressionCompletionDescription::ForExpression("number", "", 2, 2)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions3{
gd::ExpressionCompletionDescription::ForObject("number", "", 3, 3),
gd::ExpressionCompletionDescription::ForExpression("number", "", 3, 3)};
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedCompletions1);
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedCompletions2);
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedCompletions3);
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedEmptyCompletions);
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedEmptyCompletions);
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedEmptyCompletions);
}
SECTION("Operator (string)") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions3{
gd::ExpressionCompletionDescription::ForObject("string", "", 3, 3),
gd::ExpressionCompletionDescription::ForExpression("string", "", 3, 3)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions4{
gd::ExpressionCompletionDescription::ForObject("string", "", 4, 4),
gd::ExpressionCompletionDescription::ForExpression("string", "", 4, 4)};
std::vector<gd::ExpressionCompletionDescription> expectedCompletions5{
gd::ExpressionCompletionDescription::ForObject("string", "", 5, 5),
gd::ExpressionCompletionDescription::ForExpression("string", "", 5, 5)};
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) == expectedCompletions3);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) == expectedCompletions4);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) == expectedCompletions5);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) ==
expectedEmptyCompletions);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) ==
expectedEmptyCompletions);
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) ==
expectedEmptyCompletions);
}
SECTION("Free function") {
SECTION("Test 1") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Function", 0, 8)};
"string", "Function", 0, 8)};
std::vector<gd::ExpressionCompletionDescription> expectedExactCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Function", 0, 8)
"string", "Function", 0, 8)
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "Function(", 0) ==
expectedCompletions);
@@ -230,17 +215,17 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"unknown", "MyObject", 0, 8)};
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorOrFunctionCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
"Func", 9, 13, "MyObject"),
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", 9, 13, "MyObject")};
"string", "Func", 9, 13, "MyObject")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", 9, 13, "MyObject")
"string", "Func", 9, 13, "MyObject")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 0) ==
expectedObjectCompletions);
@@ -329,7 +314,7 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
std::vector<gd::ExpressionCompletionDescription>
expectedObjectCompletions{
gd::ExpressionCompletionDescription::ForObject(
"unknown", "MyObject", 0, 8)};
"string", "MyObject", 0, 8)};
std::vector<gd::ExpressionCompletionDescription>
expectedBehaviorCompletions{
gd::ExpressionCompletionDescription::ForBehavior(
@@ -337,11 +322,11 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
std::vector<gd::ExpressionCompletionDescription>
expectedFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", 21, 25, "MyObject", "MyBehavior")};
"string", "Func", 21, 25, "MyObject", "MyBehavior")};
std::vector<gd::ExpressionCompletionDescription>
expectedExactFunctionCompletions{
gd::ExpressionCompletionDescription::ForExpression(
"unknown", "Func", 21, 25, "MyObject", "MyBehavior")
"string", "Func", 21, 25, "MyObject", "MyBehavior")
.SetIsExact(true)};
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 0) ==
expectedObjectCompletions);

View File

@@ -971,6 +971,35 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "");
}
SECTION("Unfinished object function name of type string with parentheses") {
auto node = parser.ParseExpression("string", "MyObject.()");
REQUIRE(node != nullptr);
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(objectFunctionCall.type == "string");
}
SECTION("Unfinished object function name of type number with parentheses") {
auto node = parser.ParseExpression("number", "MyObject.()");
REQUIRE(node != nullptr);
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(objectFunctionCall.type == "number");
}
SECTION(
"Unfinished object function name of type number|string with "
"parentheses") {
auto node = parser.ParseExpression("number|string", "MyObject.()");
REQUIRE(node != nullptr);
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(objectFunctionCall.objectName == "MyObject");
REQUIRE(objectFunctionCall.functionName == "");
REQUIRE(objectFunctionCall.type == "number|string");
}
SECTION("Unfinished object behavior name") {
auto node = parser.ParseExpression("string", "MyObject.MyBehavior::");
REQUIRE(node != nullptr);
@@ -981,6 +1010,67 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
REQUIRE(objectFunctionName.behaviorFunctionName == "");
}
SECTION("Unfinished object behavior name of type string with parentheses") {
auto node = parser.ParseExpression("string", "MyObject.MyBehavior::()");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
REQUIRE(objectFunctionName.type == "string");
}
SECTION("Unfinished object behavior name of type number with parentheses") {
auto node = parser.ParseExpression("number", "MyObject.MyBehavior::()");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
REQUIRE(objectFunctionName.type == "number");
}
SECTION(
"Unfinished object behavior name of type number|string with "
"parentheses") {
auto node =
parser.ParseExpression("number|string", "MyObject.MyBehavior::()");
REQUIRE(node != nullptr);
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(objectFunctionName.objectName == "MyObject");
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
REQUIRE(objectFunctionName.functionName == "");
REQUIRE(objectFunctionName.type == "number|string");
}
SECTION("Unfinished free function name of type string with parentheses") {
auto node = parser.ParseExpression("string", "fun()");
REQUIRE(node != nullptr);
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(freeFunctionCall.objectName == "");
REQUIRE(freeFunctionCall.functionName == "fun");
REQUIRE(freeFunctionCall.type == "string");
}
SECTION("Unfinished free function name of type number with parentheses") {
auto node = parser.ParseExpression("number", "fun()");
REQUIRE(node != nullptr);
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(freeFunctionCall.objectName == "");
REQUIRE(freeFunctionCall.functionName == "fun");
REQUIRE(freeFunctionCall.type == "number");
}
SECTION(
"Unfinished free function name of type number|string with parentheses") {
auto node = parser.ParseExpression("number|string", "fun()");
REQUIRE(node != nullptr);
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
REQUIRE(freeFunctionCall.objectName == "");
REQUIRE(freeFunctionCall.functionName == "fun");
REQUIRE(freeFunctionCall.type == "number|string");
}
SECTION("Invalid function calls") {
{
auto node = parser.ParseExpression("number", "Idontexist(12)");

View File

@@ -2,6 +2,8 @@ namespace gdjs {
declare var admob: any;
export namespace adMob {
const logger = new gdjs.Logger('AdMob');
export enum AdSizeType {
BANNER,
LARGE_BANNER,
@@ -127,13 +129,13 @@ namespace gdjs {
() => {
bannerShowing = true;
bannerLoading = false;
console.info('AdMob banner successfully shown.');
logger.info('AdMob banner successfully shown.');
},
(error) => {
bannerShowing = false;
bannerLoading = false;
bannerErrored = true;
console.error('Error while showing an AdMob banner:', error);
logger.error('Error while showing an AdMob banner:', error);
}
);
};
@@ -190,14 +192,14 @@ namespace gdjs {
})
.then(
() => {
console.info('AdMob interstitial successfully loaded.');
logger.info('AdMob interstitial successfully loaded.');
if (displayWhenLoaded) showInterstitial();
},
(error) => {
interstitialLoading = false;
interstitialReady = false;
interstitialErrored = true;
console.error('Error while loading a interstitial:', error);
logger.error('Error while loading a interstitial:', error);
}
);
};
@@ -216,7 +218,7 @@ namespace gdjs {
(error) => {
interstitialShowing = false;
interstitialErrored = true;
console.error('Error while trying to show an interstitial:', error);
logger.error('Error while trying to show an interstitial:', error);
}
);
};
@@ -270,7 +272,7 @@ namespace gdjs {
})
.then(
() => {
console.info('AdMob reward video successfully loaded.');
logger.info('AdMob reward video successfully loaded.');
if (displayWhenLoaded) showVideo();
},
@@ -278,7 +280,7 @@ namespace gdjs {
videoLoading = false;
videoReady = false;
videoErrored = true;
console.error('Error while loading a reward video:', error);
logger.error('Error while loading a reward video:', error);
}
);
};
@@ -298,7 +300,7 @@ namespace gdjs {
(error) => {
videoShowing = false;
videoErrored = true;
console.error('Error while trying to show a reward video:', error);
logger.error('Error while trying to show a reward video:', error);
}
);
};

View File

@@ -83,6 +83,30 @@ module.exports = {
.setIncludeFile('Extensions/DebuggerTools/debuggertools.js')
.setFunctionName('gdjs.evtTools.debuggerTools.enableDebugDraw');
extension
.addAction(
'ConsoleLog',
_('Log a message to the console'),
_("Logs a message to the debugger's console."),
_(
'Log message _PARAM0_ of type _PARAM1_ to the console in group _PARAM2_'
),
_('Debugger Tools'),
'res/actions/bug32.png',
'res/actions/bug32.png'
)
.addParameter('string', 'Message to log', '', false)
.addParameter(
'stringWithSelector',
'Message type',
'["info", "warning", "error"]',
true
)
.addParameter('string', 'Group of messages', '', true)
.getCodeExtraInformation()
.setIncludeFile('Extensions/DebuggerTools/debuggertools.js')
.setFunctionName('gdjs.evtTools.debuggerTools.log');
return extension;
},
runExtensionSanityTests: function (

View File

@@ -13,6 +13,20 @@ namespace gdjs {
runtimeScene.getGame().pause(true);
};
/**
* Logs a message to the console.
* @param message - The message to log.
* @param type - The type of log (info, warning or error).
* @param group - The group of messages it belongs to.
*/
export const log = function (
message: string,
type: 'info' | 'warning' | 'error',
group: string
) {
gdjs.Logger.getLoggerOutput().log(group, message, type, false);
};
/**
* Enable or disable the debug draw.
* @param runtimeScene - The current scene.

View File

@@ -62,7 +62,7 @@ module.exports = {
"JsPlatform/Extensions/orientation_alpha24.png",
"JsPlatform/Extensions/orientation_alpha32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -82,7 +82,7 @@ module.exports = {
"JsPlatform/Extensions/orientation_beta24.png",
"JsPlatform/Extensions/orientation_beta32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -102,7 +102,7 @@ module.exports = {
"JsPlatform/Extensions/orientation_gamma24.png",
"JsPlatform/Extensions/orientation_gamma32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -228,7 +228,7 @@ module.exports = {
"JsPlatform/Extensions/motion_rotation_alpha24.png",
"JsPlatform/Extensions/motion_rotation_alpha32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -248,7 +248,7 @@ module.exports = {
"JsPlatform/Extensions/motion_rotation_beta24.png",
"JsPlatform/Extensions/motion_rotation_beta32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -268,7 +268,7 @@ module.exports = {
"JsPlatform/Extensions/motion_rotation_gamma24.png",
"JsPlatform/Extensions/motion_rotation_gamma32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -288,7 +288,7 @@ module.exports = {
"JsPlatform/Extensions/motion_acceleration_x24.png",
"JsPlatform/Extensions/motion_acceleration_x32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -308,7 +308,7 @@ module.exports = {
"JsPlatform/Extensions/motion_acceleration_y24.png",
"JsPlatform/Extensions/motion_acceleration_y32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.getCodeExtraInformation()
.setIncludeFile(
@@ -328,7 +328,7 @@ module.exports = {
"JsPlatform/Extensions/motion_acceleration_z24.png",
"JsPlatform/Extensions/motion_acceleration_z32.png"
)
.addParameter("relationalOperator", _("Sign of the test"))
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.getCodeExtraInformation()
.setIncludeFile(

View File

@@ -1,6 +1,8 @@
// @ts-nocheck - Weird usage of `this` in this file. Should be refactored.
namespace gdjs {
const logger = new gdjs.Logger('Dialogue tree');
gdjs.dialogueTree = {};
gdjs.dialogueTree.runner = new bondage.Runner();
@@ -21,7 +23,7 @@ namespace gdjs {
gdjs.dialogueTree.startFrom(startDialogueNode);
}
} catch (e) {
console.error(e);
logger.error('Error while loading from scene variable: ', e);
}
};
@@ -42,10 +44,7 @@ namespace gdjs {
.getJsonManager()
.loadJson(jsonResourceName, function (error, content) {
if (error) {
console.error(
'An error happened while loading JSON resource:',
error
);
logger.error('An error happened while loading JSON resource:', error);
} else {
if (!content) {
return;
@@ -54,7 +53,7 @@ namespace gdjs {
try {
gdjs.dialogueTree.runner.load(gdjs.dialogueTree.yarnData);
} catch (error) {
console.error(
logger.error(
'An error happened while loading parsing the dialogue tree data:',
error
);
@@ -155,7 +154,7 @@ namespace gdjs {
this.clipTextEnd >= this.dialogueText.length
) {
if (gdjs.dialogueTree.getVariable('debug')) {
console.warn(
logger.warn(
'Scroll completed:',
this.clipTextEnd,
'/',
@@ -244,7 +243,7 @@ namespace gdjs {
gdjs.dialogueTree.pauseScrolling = false;
commandCalls.splice(index, 1);
if (gdjs.dialogueTree.getVariable('debug')) {
console.info('CMD:', call);
logger.info('CMD:', call);
}
}, parseInt(call.params[1], 10));
}
@@ -252,7 +251,7 @@ namespace gdjs {
gdjs.dialogueTree.commandParameters = call.params;
commandCalls.splice(index, 1);
if (gdjs.dialogueTree.getVariable('debug')) {
console.info('CMD:', call);
logger.info('CMD:', call);
}
return true;
}
@@ -366,7 +365,7 @@ namespace gdjs {
this.dialogueData = this.dialogue.next().value;
gdjs.dialogueTree.goToNextDialogueLine();
} catch (error) {
console.error(
logger.error(
`An error happened when trying to access the dialogue branch!`,
error
);
@@ -562,7 +561,7 @@ namespace gdjs {
this.selectedOption = -1;
this.selectedOptionUpdated = false;
if (gdjs.dialogueTree.getVariable('debug')) {
console.info('parsing:', this.dialogueData);
logger.info('Parsing:', this.dialogueData);
}
if (!this.dialogueData) {
gdjs.dialogueTree.stopRunningDialogue();
@@ -797,7 +796,7 @@ namespace gdjs {
gdjs.dialogueTree.loadState = function (inputVariable: gdjs.Variable) {
const loadedState = inputVariable.toJSObject();
if (!loadedState) {
console.error('Load state variable is empty:', inputVariable);
logger.error('Load state variable is empty:', inputVariable);
return;
}
try {
@@ -808,7 +807,7 @@ namespace gdjs {
gdjs.dialogueTree.runner.variables.set(key, value);
});
} catch (e) {
console.error('Failed to load state from variable:', inputVariable, e);
logger.error('Failed to load state from variable:', inputVariable, e);
}
};

View File

@@ -179,7 +179,8 @@ module.exports = {
.setValue(
behaviorContent.getBoolAttribute('property2') ? 'true' : 'false'
)
.setType('Boolean');
.setType('Boolean')
.setGroup(_('Look and Feel'));
return behaviorProperties;
};

View File

@@ -1,5 +1,6 @@
//A simple PIXI filter doing some color changes
namespace gdjs {
const logger = new gdjs.Logger('Dummy effect');
import PIXI = GlobalPIXIModule.PIXI;
const DummyPixiFilter = function () {
@@ -46,7 +47,7 @@ namespace gdjs {
// `effectData.stringParameters.someImage`
// `effectData.stringParameters.someColor`
// `effectData.booleanParameters.someBoolean`
console.info(
logger.info(
'The PIXI texture found for the Dummy Effect (not actually used):',
(layer
.getRuntimeScene()

View File

@@ -1,4 +1,6 @@
namespace gdjs {
const logger = new gdjs.Logger('Dummy behavior');
/**
* The DummyRuntimeBehavior changes a variable in the object that is owning
* it, at every tick before events are run, to set it to the string that was
@@ -20,7 +22,7 @@ namespace gdjs {
this._textToSet = behaviorData.property1;
// You can also run arbitrary code at the creation of the behavior:
console.log('DummyRuntimeBehavior was created for object:', owner);
logger.log('DummyRuntimeBehavior was created for object:', owner);
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {

View File

@@ -1,4 +1,6 @@
namespace gdjs {
const logger = new gdjs.Logger('Dummy object');
/**
* A dummy object doing showing a text on screen.
* @ignore
@@ -126,9 +128,9 @@ namespace gdjs {
* A dummy method that can be called from events
*/
myMethod(number1: float, text1: string) {
console.log('Congrats, this method was called on a DummyRuntimeObject');
console.log('Here is the object:', this);
console.log('Here are the arguments passed from events:', number1, text1);
logger.log('Congrats, this method was called on a DummyRuntimeObject');
logger.log('Here is the object:', this);
logger.log('Here are the arguments passed from events:', number1, text1);
}
}
gdjs.registerObject(

View File

@@ -1,4 +1,5 @@
namespace gdjs {
const logger = new gdjs.Logger('Dummy behavior (with shared data)');
export class DummyWithSharedDataRuntimeBehavior extends gdjs.RuntimeBehavior {
_textToSet: string;
@@ -20,11 +21,11 @@ namespace gdjs {
this._textToSet = (sharedData as any).sharedProperty1;
// You can also run arbitrary code at the creation of the behavior:
console.log(
logger.log(
'DummyWithSharedDataRuntimeBehavior was created for object:',
owner
);
console.log('The shared data are:', sharedData);
logger.log('The shared data are:', sharedData);
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {

View File

@@ -1,4 +1,6 @@
namespace gdjs {
const logger = new gdjs.Logger('Example extension');
export namespace evtTools {
/**
* This is an example of some functions that can be used through events.
@@ -24,7 +26,7 @@ namespace gdjs {
* that will be called at this moment.
*/
gdjs.registerRuntimeSceneLoadedCallback(function (runtimeScene) {
console.log('A gdjs.RuntimeScene was loaded:', runtimeScene);
logger.log('A gdjs.RuntimeScene was loaded:', runtimeScene);
});
/**
@@ -32,7 +34,7 @@ namespace gdjs {
* that will be called at this moment.
*/
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
console.log('A gdjs.RuntimeScene was unloaded:', runtimeScene);
logger.log('A gdjs.RuntimeScene was unloaded:', runtimeScene);
});
/**
@@ -42,7 +44,7 @@ namespace gdjs {
runtimeScene,
runtimeObject
) {
console.log(
logger.log(
'A gdjs.RuntimeObject was deleted from a gdjs.RuntimeScene:',
runtimeScene,
runtimeObject
@@ -50,7 +52,7 @@ namespace gdjs {
});
// Finally, note that you can also simply run code here. Most of the time you shouldn't need it though.
console.log(
logger.log(
'gdjs.exampleJsExtension was created, with myGlobalString containing:' +
myGlobalString
);

View File

@@ -1,4 +1,5 @@
namespace gdjs {
const logger = new gdjs.Logger('Facebook instant games');
export namespace evtTools {
export namespace facebookInstantGames {
export let _preloadedInterstitial: any = null;
@@ -153,12 +154,12 @@ namespace gdjs {
.then(function () {
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoading = false;
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoaded = true;
console.info('Facebook Instant Games interstitial preloaded.');
logger.info('Facebook Instant Games interstitial preloaded.');
})
.catch(function (err) {
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoading = false;
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoaded = false;
console.error('Interstitial failed to preload: ' + err.message);
logger.error('Interstitial failed to preload: ' + err.message);
errorVariable.setString(err.message || 'Unknown error');
});
};
@@ -173,10 +174,10 @@ namespace gdjs {
gdjs.evtTools.facebookInstantGames._preloadedInterstitial
.showAsync()
.then(function () {
console.info('Facebook Instant Games interstitial shown.');
logger.info('Facebook Instant Games interstitial shown.');
})
.catch(function (err) {
console.error('Interstitial failed to show: ' + err.message);
logger.error('Interstitial failed to show: ' + err.message);
errorVariable.setString(err.message || 'Unknown error');
})
.then(function () {
@@ -207,12 +208,12 @@ namespace gdjs {
.then(function () {
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoading = false;
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoaded = true;
console.info('Facebook Instant Games rewarded video preloaded.');
logger.info('Facebook Instant Games rewarded video preloaded.');
})
.catch(function (err) {
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoading = false;
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoaded = false;
console.error('Rewarded video failed to preload: ' + err.message);
logger.error('Rewarded video failed to preload: ' + err.message);
errorVariable.setString(err.message || 'Unknown error');
});
};
@@ -227,10 +228,10 @@ namespace gdjs {
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideo
.showAsync()
.then(function () {
console.info('Facebook Instant Games rewarded video shown.');
logger.info('Facebook Instant Games rewarded video shown.');
})
.catch(function (err) {
console.error('Rewarded video failed to show: ' + err.message);
logger.error('Rewarded video failed to show: ' + err.message);
errorVariable.setString(err.message || 'Unknown error');
})
.then(function () {
@@ -242,7 +243,7 @@ namespace gdjs {
return gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoaded;
};
if (typeof FBInstant === 'undefined' && typeof window !== 'undefined') {
console.log('Creating a mocked version of Facebook Instant Games.');
logger.log('Creating a mocked version of Facebook Instant Games.');
/**
* A mocked Leaderboard, part of the mock of FBInstant.
@@ -295,7 +296,7 @@ namespace gdjs {
showAsync(): Promise<void> {
if (this._isLoaded) {
console.info(
logger.info(
'In a real Instant Game, a video reward should have been shown to the user.'
);
return Promise.resolve();
@@ -318,7 +319,7 @@ namespace gdjs {
showAsync(): Promise<void> {
if (this._isLoaded) {
console.info(
logger.info(
'In a real Instant Game, an interstitial should have been shown to the user.'
);
return Promise.resolve();

View File

@@ -415,6 +415,7 @@ module.exports = {
_('Filesystem/Windows, Linux, MacOS'),
'JsPlatform/Extensions/filesystem_folder32.png'
)
.addCodeOnlyParameter('currentScene', '')
.getCodeExtraInformation()
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
.setFunctionName('gdjs.fileSystem.getUserHomePath');

View File

@@ -1,4 +1,5 @@
namespace gdjs {
const logger = new gdjs.Logger('Filesystem');
export namespace fileSystem {
// The Node.js path module, or null if it can't be loaded.
export let _path: any = null;
@@ -149,7 +150,9 @@ namespace gdjs {
* Get the path to the user's home folder (on Windows `C:\Users\<USERNAME>\` for example).
* @return The path to user's "home" folder
*/
export const getUserHomePath = function (runtimeScene): string {
export const getUserHomePath = function (
runtimeScene: gdjs.RuntimeScene
): string {
const electron = runtimeScene.getGame().getRenderer().getElectron();
if (electron) {
return electron.remote.app.getPath('home') || '';
@@ -203,7 +206,7 @@ namespace gdjs {
fileSystem.mkdirSync(directory);
result = 'ok';
} catch (err) {
console.error(
logger.error(
"Unable to create directory at: '" + directory + "': ",
err
);
@@ -228,7 +231,7 @@ namespace gdjs {
fileSystem.writeFile(savePath, text, 'utf8', (err) => {
resultVar.setString('ok');
if (err) {
console.error(
logger.error(
"Unable to save the text to path: '" + savePath + "': ",
err
);
@@ -256,7 +259,7 @@ namespace gdjs {
fileSystem.writeFileSync(savePath, text, 'utf8');
result = 'ok';
} catch (err) {
console.error(
logger.error(
"Unable to save the text to path: '" + savePath + "': ",
err
);
@@ -287,7 +290,7 @@ namespace gdjs {
);
result = 'ok';
} catch (err) {
console.error(
logger.error(
"Unable to save the variable to path: '" + savePath + "': ",
err
);
@@ -316,7 +319,7 @@ namespace gdjs {
(err) => {
resultVar.setString('ok');
if (err) {
console.error(
logger.error(
"Unable to save the variable to path: '" + savePath + "': ",
err
);
@@ -348,7 +351,7 @@ namespace gdjs {
result = 'ok';
}
} catch (err) {
console.error(
logger.error(
"Unable to load the file at path: '" + loadPath + "': ",
err
);
@@ -378,7 +381,7 @@ namespace gdjs {
result = 'ok';
}
} catch (err) {
console.error(
logger.error(
"Unable to load variable from the file at path: '" +
loadPath +
"': ",
@@ -408,7 +411,7 @@ namespace gdjs {
resultVar.setString('ok');
}
if (err) {
console.error(
logger.error(
"Unable to load variable from the file at path: '" +
loadPath +
"': ",
@@ -439,7 +442,7 @@ namespace gdjs {
resultVar.setString('ok');
}
if (err) {
console.error(
logger.error(
"Unable to load the file at path: '" + loadPath + "': ",
err
);
@@ -465,7 +468,7 @@ namespace gdjs {
fileSystem.unlinkSync(filePath);
result = 'ok';
} catch (err) {
console.error("Unable to delete the file: '" + filePath + "': ", err);
logger.error("Unable to delete the file: '" + filePath + "': ", err);
result = 'error';
}
}
@@ -486,7 +489,7 @@ namespace gdjs {
fileSystem.unlink(filePath, (err) => {
resultVar.setString('ok');
if (err) {
console.error(
logger.error(
"Unable to delete the file: '" + filePath + "': ",
err
);

View File

@@ -1,4 +1,5 @@
namespace gdjs {
const logger = new gdjs.Logger('Firebase (setup)');
export namespace evtTools {
/**
* Firebase Event Tools
@@ -23,7 +24,7 @@ namespace gdjs {
.getExtensionProperty('Firebase', 'FirebaseConfig')
);
} catch (e) {
console.error('The Firebase configuration is invalid! Error: ' + e);
logger.error('The Firebase configuration is invalid! Error: ' + e);
return;
}
if (typeof firebaseConfig !== 'object') return;

View File

@@ -5,12 +5,7 @@ namespace gdjs {
_obstacleRBush: any;
constructor(runtimeScene: gdjs.RuntimeScene) {
this._obstacleRBush = new rbush(9, [
'.owner.getAABB().min[0]',
'.owner.getAABB().min[1]',
'.owner.getAABB().max[0]',
'.owner.getAABB().max[1]',
]);
this._obstacleRBush = new rbush();
}
/**
@@ -35,7 +30,10 @@ namespace gdjs {
* Add a light obstacle to the list of existing obstacles.
*/
addObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) {
this._obstacleRBush.insert(obstacle);
if (obstacle.currentRBushAABB)
obstacle.currentRBushAABB.updateAABBFromOwner();
else obstacle.currentRBushAABB = new gdjs.BehaviorRBushAABB(obstacle);
this._obstacleRBush.insert(obstacle.currentRBushAABB);
}
/**
@@ -43,7 +41,7 @@ namespace gdjs {
* added before.
*/
removeObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) {
this._obstacleRBush.remove(obstacle);
this._obstacleRBush.remove(obstacle.currentRBushAABB);
}
/**
@@ -55,7 +53,7 @@ namespace gdjs {
getAllObstaclesAround(
object: gdjs.RuntimeObject,
radius: number,
result: gdjs.LightObstacleRuntimeBehavior[]
result: gdjs.BehaviorRBushAABB<gdjs.LightObstacleRuntimeBehavior>[]
) {
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
// is not necessarily in the middle of the object (for sprites for example).
@@ -83,15 +81,22 @@ namespace gdjs {
_oldY: float = 0;
_oldWidth: float = 0;
_oldHeight: float = 0;
currentRBushAABB: gdjs.BehaviorRBushAABB<
LightObstacleRuntimeBehavior
> | null = null;
_manager: any;
_registeredInManager: boolean = false;
constructor(runtimeScene, behaviorData, owner) {
constructor(
runtimeScene: gdjs.RuntimeScene,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
this._manager = LightObstaclesManager.getManager(runtimeScene);
}
doStepPreEvents(runtimeScene) {
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
// Make sure the obstacle is or is not in the obstacles manager.
if (!this.activated() && this._registeredInManager) {
this._manager.removeObstacle(this);

View File

@@ -1,4 +1,5 @@
namespace gdjs {
const logger = new gdjs.Logger('Light object');
import PIXI = GlobalPIXIModule.PIXI;
/**
@@ -7,19 +8,24 @@ namespace gdjs {
export class LightRuntimeObjectPixiRenderer {
_object: gdjs.LightRuntimeObject;
_runtimeScene: gdjs.RuntimeScene;
_manager: any;
_manager: gdjs.LightObstaclesManager;
_radius: number;
_color: any;
_color: [number, number, number];
_texture: PIXI.Texture | null = null;
_center: any;
_defaultVertexBuffer: any;
_vertexBuffer: any;
_indexBuffer: any;
_center: Float32Array;
_defaultVertexBuffer: Float32Array;
_vertexBuffer: Float32Array;
_indexBuffer: Uint16Array;
_light: PIXI.Mesh<PIXI.Shader> | null = null;
_isPreview: boolean;
_debugMode: any = null;
_debugMode: boolean = false;
_debugLight: PIXI.Container | null = null;
_debugGraphics: PIXI.Graphics | null = null;
/**
* A polygon updated when vertices of the light are computed
* to be a polygon bounding the light and its obstacles.
*/
_lightBoundingPoly: gdjs.Polygon;
constructor(
@@ -52,13 +58,8 @@ namespace gdjs {
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
this.updateMesh();
this._isPreview = runtimeScene.getGame().isPreview();
this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0);
this._lightBoundingPoly = new gdjs.Polygon();
for (let i = 0; i < 4; i++) {
this._lightBoundingPoly.vertices.push(
runtimeObject.getHitBoxes()[0].vertices[i]
);
}
this.updateDebugMode();
// Objects will be added in lighting layer, this is just to maintain consistency.
@@ -84,10 +85,10 @@ namespace gdjs {
}
static _computeClosestIntersectionPoint(
lightObject,
angle,
polygons,
boundingSquareHalfDiag
lightObject: gdjs.LightRuntimeObject,
angle: float,
polygons: Array<gdjs.Polygon>,
boundingSquareHalfDiag: float
) {
const centerX = lightObject.getX();
const centerY = lightObject.getY();
@@ -134,7 +135,7 @@ namespace gdjs {
updateMesh(): void {
if (!PIXI.utils.isWebGLSupported()) {
console.warn(
logger.warn(
'This device does not support webgl, which is required for Lighting Extension.'
);
return;
@@ -307,8 +308,8 @@ namespace gdjs {
// and instead use a subarray. Otherwise, allocate new array buffers as
// there would be memory wastage.
let isSubArrayUsed = false;
let vertexBufferSubArray = null;
let indexBufferSubArray = null;
let vertexBufferSubArray: Float32Array | null = null;
let indexBufferSubArray: Uint16Array | null = null;
if (this._vertexBuffer.length > 2 * verticesCount + 2) {
if (this._vertexBuffer.length < 4 * verticesCount + 4) {
isSubArrayUsed = true;
@@ -367,8 +368,10 @@ namespace gdjs {
* Computes the vertices of mesh using raycasting.
* @returns the vertices of mesh.
*/
_computeLightVertices(): Array<any> {
const lightObstacles: Array<gdjs.LightObstacleRuntimeBehavior> = [];
_computeLightVertices(): Array<FloatPoint> {
const lightObstacles: gdjs.BehaviorRBushAABB<
LightObstacleRuntimeBehavior
>[] = [];
if (this._manager) {
this._manager.getAllObstaclesAround(
this._object,
@@ -376,38 +379,47 @@ namespace gdjs {
lightObstacles
);
}
const searchAreaLeft = this._object.getX() - this._radius;
const searchAreaTop = this._object.getY() - this._radius;
const searchAreaRight = this._object.getX() + this._radius;
const searchAreaBottom = this._object.getY() + this._radius;
// Bail out early if there are no obstacles.
if (lightObstacles.length === 0) {
// @ts-ignore TODO the array should probably be pass as a parameter.
return lightObstacles;
}
// Synchronize light bounding polygon with the hitbox.
const lightHitboxPoly = this._object.getHitBoxes()[0];
// Note: we suppose the hitbox is always a single rectangle.
const objectHitBox = this._object.getHitBoxes()[0];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 2; j++) {
this._lightBoundingPoly.vertices[i][j] =
lightHitboxPoly.vertices[i][j];
}
this._lightBoundingPoly.vertices[i][0] = objectHitBox.vertices[i][0];
this._lightBoundingPoly.vertices[i][1] = objectHitBox.vertices[i][1];
}
const obstaclesCount = lightObstacles.length;
const obstacleHitBoxes = new Array(obstaclesCount);
for (let i = 0; i < obstaclesCount; i++) {
obstacleHitBoxes[i] = lightObstacles[i].owner.getHitBoxes();
}
const obstaclePolygons: Array<any> = [];
// Create the list of polygons to compute the light vertices
const obstaclePolygons: Array<gdjs.Polygon> = [];
obstaclePolygons.push(this._lightBoundingPoly);
for (let i = 0; i < obstaclesCount; i++) {
const noOfHitBoxes = obstacleHitBoxes[i].length;
for (let j = 0; j < noOfHitBoxes; j++) {
obstaclePolygons.push(obstacleHitBoxes[i][j]);
for (let i = 0; i < lightObstacles.length; i++) {
const obstacleHitBoxes = lightObstacles[
i
].behavior.owner.getHitBoxesAround(
searchAreaLeft,
searchAreaTop,
searchAreaRight,
searchAreaBottom
);
for (const hitbox of obstacleHitBoxes) {
obstaclePolygons.push(hitbox);
}
}
let maxX = this._object.x + this._radius;
let minX = this._object.x - this._radius;
let maxY = this._object.y + this._radius;
let minY = this._object.y - this._radius;
const flattenVertices: Array<any> = [];
const flattenVertices: Array<FloatPoint> = [];
for (let i = 1; i < obstaclePolygons.length; i++) {
const vertices = obstaclePolygons[i].vertices;
const verticesCount = vertices.length;
@@ -449,6 +461,7 @@ namespace gdjs {
(maxY - this._object.y) * (maxY - this._object.y)
)
);
// Add this._object.hitBoxes vertices.
for (let i = 0; i < 4; i++) {
flattenVertices.push(obstaclePolygons[0].vertices[i]);
}
@@ -543,9 +556,11 @@ namespace gdjs {
varying vec2 vPos;
void main() {
vec2 topleft = vec2(center.x - radius, center.y - radius);
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
gl_FragColor = vec4(color, 1.0) * texture2D(uSampler, texCoord);
vec2 topleft = vec2(center.x - radius, center.y - radius);
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
gl_FragColor = (texCoord.x > 0.0 && texCoord.x < 1.0 && texCoord.y > 0.0 && texCoord.y < 1.0)
? vec4(color, 1.0) * texture2D(uSampler, texCoord)
: vec4(0.0, 0.0, 0.0, 0.0);
}`;
}

View File

@@ -149,10 +149,10 @@ namespace gdjs {
}
/**
* Get the light obstacles manager if objects with the behavior exist, null otherwise.
* @returns gdjs.LightObstaclesManager if it exists, otherwise null.
* Get the light obstacles manager.
* @returns the light obstacles manager.
*/
getObstaclesManager(): gdjs.LightObstaclesManager | null {
getObstaclesManager(): gdjs.LightObstaclesManager {
return this._obstaclesManager;
}

View File

@@ -47,8 +47,9 @@ namespace gdjs {
/**
* @returns an iterable on every object linked with objA.
*/
// : Iterable<gdjs.RuntimeObject> in practice
getObjectsLinkedWith(objA: gdjs.RuntimeObject) {
getObjectsLinkedWith(
objA: gdjs.RuntimeObject
): Iterable<gdjs.RuntimeObject> {
if (!this._links.has(objA.id)) {
this._links.set(objA.id, new IterableLinkedObjects());
}
@@ -149,19 +150,20 @@ namespace gdjs {
}
}
class IterableLinkedObjects {
class IterableLinkedObjects implements Iterable<gdjs.RuntimeObject> {
linkedObjectMap: Map<string, gdjs.RuntimeObject[]>;
static emptyItr: Iterator<gdjs.RuntimeObject> = {
next: () => ({ value: undefined, done: true }),
};
constructor() {
this.linkedObjectMap = new Map<string, gdjs.RuntimeObject[]>();
}
[Symbol.iterator]() {
let mapItr = this.linkedObjectMap.entries();
let listItr: IterableIterator<[
number,
gdjs.RuntimeObject
]> = [].entries();
let mapItr = this.linkedObjectMap.values();
let listItr: Iterator<gdjs.RuntimeObject> =
IterableLinkedObjects.emptyItr;
return {
next: () => {
@@ -169,15 +171,12 @@ namespace gdjs {
while (listNext.done) {
const mapNext = mapItr.next();
if (mapNext.done) {
// IteratorReturnResult<gdjs.RuntimeObject> require a defined value
// even though the spec state otherwise.
// So, this class can't be typed as an iterable.
return { value: undefined, done: true };
return listNext;
}
listItr = mapNext.value[1].entries();
listItr = mapNext.value[Symbol.iterator]();
listNext = listItr.next();
}
return { value: listNext.value[1], done: false };
return listNext;
},
};
}

View File

@@ -69,3 +69,4 @@ var t=require("./bufferbuilder").BufferBuilder,e=require("./bufferbuilder").bina
},{"eventemitter3":"JJlS","./util":"BHXf","./logger":"WOs9","./socket":"wJlv","./mediaconnection":"dbHP","./dataconnection":"GBTQ","./enums":"ZRYf","./api":"in7L"}],"iTK6":[function(require,module,exports) {
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./util"),r=require("./peer");exports.peerjs={Peer:r.Peer,util:e.util},exports.default=r.Peer,window.peerjs=exports.peerjs,window.Peer=r.Peer;
},{"./util":"BHXf","./peer":"Hxpd"}]},{},["iTK6"], null)
//# sourceMappingURL=A_peer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -375,15 +375,14 @@ namespace gdjs {
key: string,
ssl: boolean
) => {
peerConfig = {
debug: 1,
Object.assign(peerConfig, {
host,
port,
path,
secure: ssl,
// All servers have "peerjs" as default key
key: key.length === 0 ? 'peerjs' : key,
};
});
loadPeerJS();
};
@@ -394,6 +393,26 @@ namespace gdjs {
*/
export const useDefaultBrokerServer = loadPeerJS;
/**
* Adds an ICE server candidate, and removes the default ones provided by PeerJs. Must be called before connecting to a broker.
* @param urls The URL of the STUN/TURN server.
* @param username An optional username to send to the server.
* @param credential An optional password to send to the server.
*/
export const useCustomICECandidate = (
urls: string,
username?: string,
credential?: string
) => {
peerConfig.config = peerConfig.config || {};
peerConfig.config.iceServers = peerConfig.config.iceServers || [];
peerConfig.config.iceServers.push({
urls,
username,
credential,
});
};
/**
* Overrides the default peer ID. Must be called before connecting to a broker.
* Overriding the ID may have unwanted consequences. Do not use this feature

View File

@@ -154,6 +154,30 @@ module.exports = {
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.useCustomBrokerServer');
extension
.addAction(
'UseOwnICEServer',
_('Use a custom ICE server'),
_(
'Disables the default ICE (STUN or TURN) servers list and use one of your own. ' +
'Note that it is recommended to add at least 1 self-hosted STUN and TURN server ' +
'for games that are not over LAN but over the internet. ' +
'This action can be used multiple times to add multiple servers. ' +
'This action needs to be called BEFORE connecting to the broker server.'
),
_('Use ICE server _PARAM0_ (username: _PARAM1_, password: _PARAM2_)'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('URL to the ICE server'), '', false)
.addParameter('string', _('(Optional) Username'), '', true)
.addParameter('string', _('(Optional) Password'), '', true)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.useCustomICECandidate');
extension
.addAction(
'UseDefaultBroker',

View File

@@ -168,7 +168,7 @@ void ExtensionSubDeclaration2(gd::ObjectMetadata& obj) {
.UseStandardRelationalOperatorParameters("number");
obj.AddAction("ParticleSize1",
_("SIze, parameter 1"),
_("Size, parameter 1"),
_("Modify parameter 1 of the size of particles"),
_("the parameter 1 of size"),
_("Common"),

View File

@@ -39,23 +39,23 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
properties[_("Max. speed")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
properties[_("Rotate speed")].SetValue(
properties[_("Rotate speed")].SetGroup(_("Rotation")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("angularMaxSpeed")));
properties[_("Rotate object")]
properties[_("Rotate object")].SetGroup(_("Rotation"))
.SetValue(behaviorContent.GetBoolAttribute("rotateObject") ? "true"
: "false")
.SetType("Boolean");
properties[_("Angle offset")].SetValue(
properties[_("Angle offset")].SetGroup(_("Rotation")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
properties[_("Virtual cell width")].SetValue(
properties[_("Virtual cell width")].SetGroup(_("Virtual Grid")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("cellWidth", 0)));
properties[_("Virtual cell height")].SetValue(
properties[_("Virtual cell height")].SetGroup(_("Virtual Grid")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("cellHeight", 0)));
properties[_("Virtual grid X offset")].SetValue(
properties[_("Virtual grid X offset")].SetGroup(_("Virtual Grid")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetX", 0)));
properties[_("Virtual grid Y offset")].SetValue(
properties[_("Virtual grid Y offset")].SetGroup(_("Virtual Grid")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetY", 0)));
properties[_("Extra border size")].SetValue(
properties[_("Extra border size")].SetGroup(_("Collision")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("extraBorder")));
return properties;

View File

@@ -19,12 +19,7 @@ namespace gdjs {
* @param object The object
*/
constructor(runtimeScene: gdjs.RuntimeScene) {
this._obstaclesRBush = new rbush(9, [
'.owner.getAABB().min[0]',
'.owner.getAABB().min[1]',
'.owner.getAABB().max[0]',
'.owner.getAABB().max[1]',
]);
this._obstaclesRBush = new rbush();
}
/**
@@ -46,7 +41,14 @@ namespace gdjs {
addObstacle(
pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior
) {
this._obstaclesRBush.insert(pathfindingObstacleBehavior);
if (pathfindingObstacleBehavior.currentRBushAABB)
pathfindingObstacleBehavior.currentRBushAABB.updateAABBFromOwner();
else
pathfindingObstacleBehavior.currentRBushAABB = new gdjs.BehaviorRBushAABB(
pathfindingObstacleBehavior
);
this._obstaclesRBush.insert(pathfindingObstacleBehavior.currentRBushAABB);
}
/**
@@ -56,7 +58,7 @@ namespace gdjs {
removeObstacle(
pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior
) {
this._obstaclesRBush.remove(pathfindingObstacleBehavior);
this._obstaclesRBush.remove(pathfindingObstacleBehavior.currentRBushAABB);
}
/**
@@ -68,7 +70,7 @@ namespace gdjs {
x: float,
y: float,
radius: float,
result: gdjs.PathfindingObstacleRuntimeBehavior[]
result: gdjs.BehaviorRBushAABB<gdjs.PathfindingObstacleRuntimeBehavior>[]
): any {
const searchArea = gdjs.staticObject(
PathfindingObstaclesManager.prototype.getAllObstaclesAround
@@ -100,6 +102,9 @@ namespace gdjs {
_oldHeight: float = 0;
_manager: PathfindingObstaclesManager;
_registeredInManager: boolean = false;
currentRBushAABB: gdjs.BehaviorRBushAABB<
PathfindingObstacleRuntimeBehavior
> | null = null;
constructor(
runtimeScene: gdjs.RuntimeScene,

View File

@@ -3,6 +3,7 @@ GDevelop - Pathfinding Behavior Extension
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
const logger = new gdjs.Logger('Pathfinding behavior');
/**
* PathfindingRuntimeBehavior represents a behavior allowing objects to
* follow a path computed to avoid obstacles.
@@ -27,8 +28,8 @@ namespace gdjs {
_pathFound: boolean = false;
_speed: float = 0;
_angularSpeed: float = 0;
_timeOnSegment: float = 0;
_totalSegmentTime: float = 0;
_distanceOnSegment: float = 0;
_totalSegmentDistance: float = 0;
_currentSegment: integer = 0;
_reachedEnd: boolean = false;
_manager: PathfindingObstaclesManager;
@@ -391,11 +392,11 @@ namespace gdjs {
const pathY =
this._path[this._currentSegment + 1][1] -
this._path[this._currentSegment][1];
this._totalSegmentTime = Math.sqrt(pathX * pathX + pathY * pathY);
this._timeOnSegment = 0;
this._totalSegmentDistance = Math.sqrt(pathX * pathX + pathY * pathY);
this._distanceOnSegment = 0;
this._reachedEnd = false;
this._movementAngle =
((Math.atan2(pathY, pathX) * 180) / Math.PI + 360) % 360;
(gdjs.toDegrees(Math.atan2(pathY, pathX)) + 360) % 360;
} else {
this._reachedEnd = true;
this._speed = 0;
@@ -407,58 +408,59 @@ namespace gdjs {
return;
}
//Update the speed of the object
// Update the speed of the object
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
this._speed += this._acceleration * timeDelta;
if (this._speed > this._maxSpeed) {
this._speed = this._maxSpeed;
const previousSpeed = this._speed;
if (this._speed !== this._maxSpeed) {
this._speed += this._acceleration * timeDelta;
if (this._speed > this._maxSpeed) {
this._speed = this._maxSpeed;
}
}
this._angularSpeed = this._angularMaxSpeed;
//Update the time on the segment and change segment if needed
this._timeOnSegment += this._speed * timeDelta;
// Update the time on the segment and change segment if needed
// Use a Verlet integration to be frame rate independent.
this._distanceOnSegment +=
((this._speed + previousSpeed) / 2) * timeDelta;
const remainingDistanceOnSegment =
this._totalSegmentDistance - this._distanceOnSegment;
if (
this._timeOnSegment >= this._totalSegmentTime &&
remainingDistanceOnSegment <= 0 &&
this._currentSegment < this._path.length
) {
this._enterSegment(this._currentSegment + 1);
this._distanceOnSegment = -remainingDistanceOnSegment;
}
//Position object on the segment and update its angle
// Position object on the segment and update its angle
let newPos = [0, 0];
let pathAngle = this.owner.getAngle();
if (this._currentSegment < this._path.length - 1) {
newPos[0] = gdjs.evtTools.common.lerp(
this._path[this._currentSegment][0],
this._path[this._currentSegment + 1][0],
this._timeOnSegment / this._totalSegmentTime
this._distanceOnSegment / this._totalSegmentDistance
);
newPos[1] = gdjs.evtTools.common.lerp(
this._path[this._currentSegment][1],
this._path[this._currentSegment + 1][1],
this._timeOnSegment / this._totalSegmentTime
this._distanceOnSegment / this._totalSegmentDistance
);
pathAngle =
gdjs.toDegrees(
Math.atan2(
this._path[this._currentSegment + 1][1] -
this._path[this._currentSegment][1],
this._path[this._currentSegment + 1][0] -
this._path[this._currentSegment][0]
)
) + this._angleOffset;
if (
this._rotateObject &&
this.owner.getAngle() !== this._movementAngle + this._angleOffset
) {
this.owner.rotateTowardAngle(
this._movementAngle + this._angleOffset,
this._angularSpeed,
runtimeScene
);
}
} else {
newPos = this._path[this._path.length - 1];
}
this.owner.setX(newPos[0]);
this.owner.setY(newPos[1]);
if (this._rotateObject) {
this.owner.rotateTowardAngle(
pathAngle,
this._angularSpeed,
runtimeScene
);
}
}
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
@@ -542,7 +544,9 @@ namespace gdjs {
//An array of nodes sorted by their estimate cost (First node = Lower estimate cost).
_openNodes: Node[] = [];
//Used by getNodes to temporarily store obstacles near a position.
_closeObstacles: PathfindingObstacleRuntimeBehavior[] = [];
_closeObstacles: gdjs.BehaviorRBushAABB<
PathfindingObstacleRuntimeBehavior
>[] = [];
//Old nodes constructed in a previous search are stored here to avoid temporary objects (see _freeAllNodes method).
_nodeCache: Node[] = [];
@@ -612,7 +616,7 @@ namespace gdjs {
computePathTo(targetX: float, targetY: float) {
if (this._obstacles === null) {
console.log(
logger.log(
'You tried to compute a path without specifying the obstacles'
);
return;
@@ -783,7 +787,7 @@ namespace gdjs {
this._closeObstacles
);
for (let k = 0; k < this._closeObstacles.length; ++k) {
const obj = this._closeObstacles[k].owner;
const obj = this._closeObstacles[k].behavior.owner;
const topLeftCellX = Math.floor(
(obj.getDrawableX() - this._rightBorder - this._gridOffsetX) /
this._cellWidth
@@ -813,13 +817,13 @@ namespace gdjs {
yPos < bottomRightCellY
) {
objectsOnCell = true;
if (this._closeObstacles[k].isImpassable()) {
if (this._closeObstacles[k].behavior.isImpassable()) {
//The cell is impassable, stop here.
newNode.cost = -1;
break;
} else {
//Superimpose obstacles
newNode.cost += this._closeObstacles[k].getCost();
newNode.cost += this._closeObstacles[k].behavior.getCost();
}
}
}

View File

@@ -1,10 +1,11 @@
// @ts-check
describe('gdjs.PathfindingRuntimeBehavior', function () {
describe.only('gdjs.PathfindingRuntimeBehavior', function () {
const epsilon = 1 / (2 << 16);
// tests cases where every collisionMethod has the same behavior.
let doCommonPathFindingTests = (collisionMethod, allowDiagonals) => {
const pathFindingName = 'auto1';
const createScene = () => {
const createScene = (framePerSecond = 60) => {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
// @ts-ignore - missing properties.
@@ -39,11 +40,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
objects: [],
instances: [],
});
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / 60) * 1000;
};
setFramePerSecond(runtimeScene, framePerSecond);
return runtimeScene;
};
const setFramePerSecond = (runtimeScene, framePerSecond) => {
runtimeScene._timeManager.getElapsedTime = function () {
return 1000 / framePerSecond;
};
};
const addPlayer = (runtimeScene) => {
const player = new gdjs.RuntimeObject(runtimeScene, {
@@ -156,6 +160,96 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
);
});
if (allowDiagonals) {
[20, 30, 60, 120].forEach((framePerSecond) => {
describe(`(${framePerSecond} fps)`, function () {
it('can move on the path at the right speed', function () {
setFramePerSecond(runtimeScene, framePerSecond);
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(600, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / framePerSecond);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(
player.getBehavior(pathFindingName).getNodeCount()
).to.be.above(13);
// Move on the path and stop before the last 1/10 of second.
for (let i = 0; i < (framePerSecond * 17) / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(false);
}
// The position is the same no matter the frame rate.
expect(player.getX()).to.be(720);
expect(player.getY()).to.be.within(
288.5786437626905 - epsilon,
288.5786437626905 + epsilon
);
// Let 1/10 of second pass,
// because the calculus interval is not the same for each case.
for (let i = 0; i < framePerSecond / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
}
// The destination is reached for every frame rate within 1/10 of second.
expect(player.getX()).to.be(720);
expect(player.getY()).to.be(300);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(true);
});
});
});
} else {
[20, 30, 60, 120].forEach((framePerSecond) => {
describe(`(${framePerSecond} fps)`, function () {
it('can move on the path at the right speed', function () {
setFramePerSecond(runtimeScene, framePerSecond);
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(600, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / framePerSecond);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(
player.getBehavior(pathFindingName).getNodeCount()
).to.be.above(13);
// Move on the path and stop before the last 1/10 of second.
for (let i = 0; i < (framePerSecond * 20) / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(false);
}
expect(player.getX()).to.be(710);
expect(player.getY()).to.be.within(300 - epsilon, 300 + epsilon);
// Let 1/10 of second pass,
// because the calculus interval is not the same for each case.
for (let i = 0; i < (framePerSecond * 1) / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
}
// The destination is reached for every frame rate within 1/10 of second.
expect(player.getX()).to.be(720);
expect(player.getY()).to.be(300);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(true);
});
});
});
}
it('can find a path between 2 obstacles', function () {
const obstacleTop = addObstacle(runtimeScene);
const obstacleBottom = addObstacle(runtimeScene);

View File

@@ -500,6 +500,20 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsAdvanced()
.SetFunctionName("SimulateControl");
aut.AddScopedCondition("IsUsingControl",
_("Control pressed or simulated"),
_("A control was applied from a default control or a simulated by an action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_("Controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.AddParameter("stringWithSelector",
_("Key"),
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
.MarkAsAdvanced();
aut.AddAction("IgnoreDefaultControls",
_("Ignore default controls"),
_("De/activate the use of default controls.\nIf deactivated, "

View File

@@ -147,6 +147,8 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
"simulateReleasePlatformKey");
autActions["PlatformBehavior::SimulateControl"].SetFunctionName(
"simulateControl");
autConditions["PlatformBehavior::PlatformerObjectBehavior::IsUsingControl"].SetFunctionName(
"isUsingControl");
autActions["PlatformBehavior::IgnoreDefaultControls"].SetFunctionName(
"ignoreDefaultControls");
}

View File

@@ -42,12 +42,12 @@ std::map<gd::String, gd::PropertyDescriptor> PlatformBehavior::GetProperties(
.AddExtraInfo(_("Platform"))
.AddExtraInfo(_("Jumpthru platform"))
.AddExtraInfo(_("Ladder"));
properties[_("Ledges can be grabbed")]
properties[_("Ledges can be grabbed")].SetGroup(_("Ledge"))
.SetValue(behaviorContent.GetBoolAttribute("canBeGrabbed", true)
? "true"
: "false")
.SetType("Boolean");
properties[_("Grab offset on Y axis")].SetValue(
properties[_("Grab offset on Y axis")].SetGroup(_("Ledge")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
return properties;

View File

@@ -20,7 +20,6 @@ This project is released under the MIT License.
void PlatformerObjectBehavior::InitializeContent(
gd::SerializerElement& behaviorContent) {
behaviorContent.SetAttribute("roundCoordinates", true);
behaviorContent.SetAttribute("gravity", 1000);
behaviorContent.SetAttribute("maxFallingSpeed", 700);
behaviorContent.SetAttribute("ladderClimbingSpeed", 150);
@@ -34,6 +33,7 @@ void PlatformerObjectBehavior::InitializeContent(
behaviorContent.SetAttribute("canGrabPlatforms", false);
behaviorContent.SetAttribute("yGrabOffset", 0);
behaviorContent.SetAttribute("xGrabTolerance", 10);
behaviorContent.SetAttribute("useLegacyTrajectory", false);
}
#if defined(GD_IDE_ONLY)
@@ -42,11 +42,11 @@ PlatformerObjectBehavior::GetProperties(
const gd::SerializerElement& behaviorContent) const {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Gravity")].SetValue(
properties[_("Gravity")].SetGroup(_("Jump")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("gravity")));
properties[_("Jump speed")].SetValue(
properties[_("Jump speed")].SetGroup(_("Jump")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("jumpSpeed")));
properties["jumpSustainTime"]
properties["jumpSustainTime"].SetGroup(_("Jump"))
.SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("jumpSustainTime", 0)))
.SetLabel(_("Jump sustain time"))
@@ -54,38 +54,39 @@ PlatformerObjectBehavior::GetProperties(
_("Maximum time (in seconds) during which the jump strength is "
"sustained if the jump key is held - allowing variable height "
"jumps."));
properties[_("Max. falling speed")].SetValue(
properties[_("Max. falling speed")].SetGroup(_("Jump")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("maxFallingSpeed")));
properties[_("Ladder climbing speed")].SetValue(gd::String::From(
properties[_("Ladder climbing speed")].SetGroup(_("Ladder")).SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("ladderClimbingSpeed", 150)));
properties[_("Acceleration")].SetValue(
properties[_("Acceleration")].SetGroup(_("Walk")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
properties[_("Deceleration")].SetValue(
properties[_("Deceleration")].SetGroup(_("Walk")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("deceleration")));
properties[_("Max. speed")].SetValue(
properties[_("Max. speed")].SetGroup(_("Walk")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
properties[_("Default controls")]
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
? "false"
: "true")
.SetType("Boolean");
properties[_("Slope max. angle")].SetValue(
properties[_("Slope max. angle")].SetGroup(_("Walk")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("slopeMaxAngle")));
properties[_("Can grab platform ledges")]
.SetGroup(_("Ledge"))
.SetValue(behaviorContent.GetBoolAttribute("canGrabPlatforms", false)
? "true"
: "false")
.SetType("Boolean");
properties[_("Grab offset on Y axis")].SetValue(
properties[_("Grab offset on Y axis")].SetGroup(_("Ledge")).SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
properties[_("Grab tolerance on X axis")].SetValue(gd::String::From(
properties[_("Grab tolerance on X axis")].SetGroup(_("Ledge")).SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("xGrabTolerance", 10)));
properties[_("Round coordinates")]
.SetValue(behaviorContent.GetBoolAttribute("roundCoordinates", false)
properties[_("Use frame per second dependent trajectories (deprecated)")]
.SetGroup(_("Jump"))
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
? "true"
: "false")
.SetType("Boolean");
return properties;
}
@@ -95,10 +96,10 @@ bool PlatformerObjectBehavior::UpdateProperty(
const gd::String& value) {
if (name == _("Default controls"))
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "0"));
else if (name == _("Round coordinates"))
behaviorContent.SetAttribute("roundCoordinates", (value == "1"));
else if (name == _("Can grab platform ledges"))
behaviorContent.SetAttribute("canGrabPlatforms", (value == "1"));
else if (name == _("Use frame per second dependent trajectories (deprecated)"))
behaviorContent.SetAttribute("useLegacyTrajectory", (value == "1"));
else if (name == _("Grab offset on Y axis"))
behaviorContent.SetAttribute("yGrabOffset", value.To<double>());
else {

View File

@@ -0,0 +1,57 @@
# Platformer Extension technical documentation
## Floor following
### Horizontal search
When the character walks on a platform, he must follow its slope.
The `slopeMaxAngle` property is used to calculate how much the character can move vertically to follow it.
If the platform is too high, the platform is considered to be an obstacle and the character will stop before it.
When there is no obstacle detected by the horizontal search, the movement is done in 1 step and the vertical search is done at the new `x` position.
[![RequestedDeltaX](./diagrams/SlopeFollowingRequestedDeltaX.png)](./diagrams/SlopeFollowingRequestedDeltaX.svgz)
Otherwise, when there is a junction, 2 vertical searches are done:
- one before a potential obstacle (in pink)
- one at the end of the movement
[![RequestedDeltaX](./diagrams/SlopeFollowingClimbFactor.png)](./diagrams/SlopeFollowingClimbFactor.svgz)
This allows to calculate the right slope angle. Indeed, in one step, the angle could appear lower (the dotted line).
Which means that the character could climb it during 1 frame and then stop.
[![RequestedDeltaX](./diagrams/SlopeFollowingClimbFactorMean.png)](./diagrams/SlopeFollowingClimbFactorMean.svgz)
For further details on the implementation, please take a look at the comments in:
- the function `gdjs.PlatformerObjectRuntimeBehavior._moveX`
- the function `gdjs.PlatformerObjectRuntimeBehavior.OnFloor.beforeMovingY`
### Vertical search
The aim of the vertical search is to find the highest platform where the character can land.
There are 2 constraints:
- `allowedMinDeltaY` how much the character can go upward
- `allowedMaxDeltaY` how much the character can go downward
During the search, these 2 constraints can tighten around the character.
If they become incompatible, it means that the character can't go through the hole,
it will go back to its original position and lose its speed.
There are also more obvious obstacles that cover the character in the middle and end the search directly.
[![RequestedDeltaX](./diagrams/SlopeFollowingResult.png)](./diagrams/SlopeFollowingResult.svgz)
Obstacles can eventually encompass the character. So platforms edges don't have any collision with character.
To detect such cases, 2 flags are used:
- `foundOverHead` when an edge is over `headMaxY`
- `foundUnderHead` when an edge is under `floorMinY`
[![RequestedDeltaX](./diagrams/SlopeFollowingContext.png)](./diagrams/SlopeFollowingContext.svgz)
For further details on the implementation, please take a look at the comments in:
- the function `gdjs.PlatformerObjectRuntimeBehavior._findHighestFloorAndMoveOnTop`
- the class `gdjs.PlatformerObjectRuntimeBehavior.FollowConstraintContext`

View File

@@ -5,7 +5,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior Benchmark', function () {
const stepCount = 6000;
beforeEach(function () {
runtimeScene = makeTestRuntimeScene();
runtimeScene = makePlatformerTestRuntimeScene();
objects = new Array(duplicateCount);
for (let i = 0; i < duplicateCount; ++i) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
declare var rbush: any;
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
/**
* Manages the common objects shared by objects having a
@@ -18,12 +19,7 @@ namespace gdjs {
* @param object The object
*/
constructor(runtimeScene: gdjs.RuntimeScene) {
this._platformRBush = new rbush(9, [
'.owner.getAABB().min[0]',
'.owner.getAABB().min[1]',
'.owner.getAABB().max[0]',
'.owner.getAABB().max[1]',
]);
this._platformRBush = new rbush();
}
/**
@@ -46,7 +42,13 @@ namespace gdjs {
* Add a platform to the list of existing platforms.
*/
addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
this._platformRBush.insert(platformBehavior);
if (platformBehavior.currentRBushAABB)
platformBehavior.currentRBushAABB.updateAABBFromOwner();
else
platformBehavior.currentRBushAABB = new gdjs.BehaviorRBushAABB(
platformBehavior
);
this._platformRBush.insert(platformBehavior.currentRBushAABB);
}
/**
@@ -54,7 +56,7 @@ namespace gdjs {
* added before.
*/
removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
this._platformRBush.remove(platformBehavior);
this._platformRBush.remove(platformBehavior.currentRBushAABB);
}
/**
@@ -65,7 +67,7 @@ namespace gdjs {
getAllPlatformsAround(
object: gdjs.RuntimeObject,
maxMovementLength: number,
result: gdjs.PlatformRuntimeBehavior[]
result: gdjs.BehaviorRBushAABB<PlatformRuntimeBehavior>[]
): any {
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
// is not necessarily in the middle of the object (for sprites for example).
@@ -73,20 +75,37 @@ namespace gdjs {
const oh = object.getHeight();
const x = object.getDrawableX() + object.getCenterX();
const y = object.getDrawableY() + object.getCenterY();
const searchArea = gdjs.staticObject(
const searchArea: SearchArea = gdjs.staticObject(
PlatformObjectsManager.prototype.getAllPlatformsAround
);
// @ts-ignore
) as SearchArea;
searchArea.minX = x - ow / 2 - maxMovementLength;
// @ts-ignore
searchArea.minY = y - oh / 2 - maxMovementLength;
// @ts-ignore
searchArea.maxX = x + ow / 2 + maxMovementLength;
// @ts-ignore
searchArea.maxY = y + oh / 2 + maxMovementLength;
const nearbyPlatforms = this._platformRBush.search(searchArea);
const nearbyPlatforms: gdjs.BehaviorRBushAABB<
PlatformRuntimeBehavior
>[] = this._platformRBush.search(searchArea);
result.length = 0;
result.push.apply(result, nearbyPlatforms);
// Extra check on the platform owner AABB
// TODO: PR https://github.com/4ian/GDevelop/pull/2602 should remove the need
// for this extra check once merged.
for (let i = 0; i < nearbyPlatforms.length; i++) {
const platform = nearbyPlatforms[i];
const platformAABB = platform.behavior.owner.getAABB();
const platformIsStillAround =
platformAABB.min[0] <= searchArea.maxX &&
platformAABB.min[1] <= searchArea.maxY &&
platformAABB.max[0] >= searchArea.minX &&
platformAABB.max[1] >= searchArea.minY;
// Filter platforms that are not in the searched area anymore.
// This can happen because platforms are not updated in the RBush before that
// characters movement are being processed.
if (platformIsStillAround) {
result.push(platform);
}
}
}
}
@@ -105,6 +124,10 @@ namespace gdjs {
_oldY: float = 0;
_oldWidth: float = 0;
_oldHeight: float = 0;
_oldAngle: float = 0;
currentRBushAABB: gdjs.BehaviorRBushAABB<
PlatformRuntimeBehavior
> | null = null;
_manager: gdjs.PlatformObjectsManager;
_registeredInManager: boolean = false;
@@ -173,7 +196,8 @@ namespace gdjs {
this._oldX !== this.owner.getX() ||
this._oldY !== this.owner.getY() ||
this._oldWidth !== this.owner.getWidth() ||
this._oldHeight !== this.owner.getHeight()
this._oldHeight !== this.owner.getHeight() ||
this._oldAngle !== this.owner.getAngle()
) {
if (this._registeredInManager) {
this._manager.removePlatform(this);
@@ -183,6 +207,7 @@ namespace gdjs {
this._oldY = this.owner.getY();
this._oldWidth = this.owner.getWidth();
this._oldHeight = this.owner.getHeight();
this._oldAngle = this.owner.getAngle();
}
}

View File

@@ -0,0 +1,783 @@
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
const epsilon = 1 / (2 << 16);
[0, 60].forEach((slopeMaxAngle) => {
describe(`(walk on flat floors, slopeMaxAngle: ${slopeMaxAngle}°)`, function () {
let runtimeScene;
let object;
beforeEach(function () {
runtimeScene = makePlatformerTestRuntimeScene();
// Put a platformer object on a platform
object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformerObjectBehavior',
name: 'auto1',
gravity: 1500,
maxFallingSpeed: 1500,
acceleration: 500,
deceleration: 1500,
maxSpeed: 500,
jumpSpeed: 900,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: slopeMaxAngle,
jumpSustainTime: 0.2,
},
],
effects: [],
});
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
});
const fall = (frameCount) => {
for (let i = 0; i < frameCount; ++i) {
const lastY = object.getY();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
true
);
expect(object.getBehavior('auto1').isMoving()).to.be(true);
expect(object.getY()).to.be.above(lastY);
}
};
const walkRight = (frameCount) => {
const behavior = object.getBehavior('auto1');
for (let i = 0; i < frameCount; ++i) {
const lastX = object.getX();
const lastSpeed = behavior.getCurrentSpeed();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isOnFloor()).to.be(true);
expect(object.getX()).to.be.above(lastX);
// Check that the object doesn't stop
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
}
};
const fallOnPlatform = (maxFrameCount) => {
// Ensure the object falls on the platform
for (let i = 0; i < maxFrameCount; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
//Check the object is on the platform
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isMoving()).to.be(false);
};
const slopesDimensions = {
26: { width: 50, height: 25 },
45: { width: 50, height: 50 },
};
it('can walk from a platform to another one', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
const platform2 = addPlatformObject(runtimeScene);
platform2.setPosition(
platform.getX() + platform.getWidth(),
platform.getY()
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Walk from the 1st platform to the 2nd one.
walkRight(30);
expect(object.getX()).to.be.above(platform2.getX());
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
});
it('can walk from a platform to a jump through', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
jumpThroughPlatform.setPosition(
platform.getX() + platform.getWidth(),
platform.getY()
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Walk from the 1st platform to the 2nd one.
walkRight(30);
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
expect(object.getY()).to.be(
jumpThroughPlatform.getY() - object.getHeight()
);
});
it('can walk on a platform and go through a jump through', function () {
// Jumpthru that are ignored had a side effects on the search context.
// It made jumpthru appear solid when a platform was tested after them.
// Add the jumptru 1st to make RBrush gives it 1st.
// There is no causality but it does in the current implementation.
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
jumpThroughPlatform.setPosition(30, -15);
jumpThroughPlatform.setCustomWidthAndHeight(60, 10);
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
object.setPosition(10, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Walk from the 1st platform to the 2nd one.
walkRight(20);
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
expect(object.getY()).to.be(platform.getY() - object.getHeight());
});
it('can walk from a platform to another one that not aligned', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
const platform2 = addPlatformObject(runtimeScene);
platform2.setPosition(
platform.getX() + platform.getWidth(),
// the 2nd platform is 1 pixel higher
platform.getY() - 1
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Walk from the 1st platform to the 2nd one.
walkRight(30);
expect(object.getX()).to.be.above(platform2.getX());
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
});
it('can walk from a platform to another one with a speed under 1 pixel/second', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
const platform2 = addPlatformObject(runtimeScene);
platform2.setPosition(
platform.getX() + platform.getWidth(),
// The 2nd platform is 1 pixels higher.
platform.getY() - 1
);
// Put the object just to the left of platform2 so that
// it try climbing on it with a very small speed.
object.setPosition(platform2.getX() - object.getWidth(), -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Walk from the 1st platform to the 2nd one.
walkRight(30);
expect(object.getX()).to.be.above(platform2.getX());
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
});
it("can't walk from a platform to another one that is a bit too high", function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
const platform2 = addPlatformObject(runtimeScene);
platform2.setPosition(
platform.getX() + platform.getWidth(),
// The 2nd platform is 2 pixels higher.
platform.getY() - 2
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// walk right
for (let i = 0; i < 20; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
}
// is blocked by the 2nd platform
expect(object.getX()).to.be(platform2.getX() - object.getWidth());
expect(object.getY()).to.be(platform.getY() - object.getHeight());
});
it('can walk on a platform and be blocked by a wall', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
// the 2nd platform is 2 pixels higher
const platform2 = addPlatformObject(runtimeScene);
platform2.setPosition(
platform.getX() + platform.getWidth(),
// The platform's top is over the object
// and platform's bottom is under the object.
platform.getY() - platform2.getHeight() + 5
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// walk right
for (let i = 0; i < 20; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
}
// is blocked by the 2nd platform
expect(object.getX()).to.be(platform2.getX() - object.getWidth());
expect(object.getY()).to.be(platform.getY() - object.getHeight());
});
it('can walk from a platform and fell through a jump through that is at the right but 1 pixel higher', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
jumpThroughPlatform.setPosition(
platform.getX() + platform.getWidth(),
// Even 1 pixel is too high to follow a jump through
// because it's like it's gone through its right or left side.
platform.getY() - 1
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Walk right
for (let i = 0; i < 20; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
}
// Fall under the jump through platform
for (let i = 0; i < 11; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
}
expect(object.getY()).to.be.above(platform.getY());
});
it('can walk inside a tunnel platform', function () {
// Put a platform.
const platform = addTunnelPlatformObject(runtimeScene);
platform.setPosition(0, 0);
object.setPosition(0, 160);
// The object falls on the bottom part of the platform
fallOnPlatform(10);
expect(object.getY()).to.be(200 - object.getHeight());
// The object walk on the bottom part of the platform.
walkRight(30);
expect(object.getX()).to.be.above(60);
expect(object.getY()).to.be(200 - object.getHeight());
});
});
});
[
// less than 1 pixel per frame (50/60)
50,
// a commonly used value
1500,
].forEach((maxFallingSpeed) => {
describe(`(on floor, maxFallingSpeed=${
maxFallingSpeed / 60
} pixels per frame)`, function () {
let runtimeScene;
let object;
let platform;
beforeEach(function () {
runtimeScene = makePlatformerTestRuntimeScene();
// Put a platformer object in the air.
object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformerObjectBehavior',
name: 'auto1',
gravity: 900,
maxFallingSpeed: maxFallingSpeed,
acceleration: 500,
deceleration: 1500,
maxSpeed: 500,
jumpSpeed: 1500,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: 60,
},
],
effects: [],
});
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
object.setPosition(0, -100);
// Put a platform.
platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
});
// TODO The character falls one frame then land instead of staying on the platform.
it.skip('must not move when on the floor at startup', function () {
object.setPosition(0, platform.getY() - object.getHeight());
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
// Check the platformer object stays still.
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
false
);
expect(object.getBehavior('auto1').isMoving()).to.be(false);
}
});
it('must not move when put on a platform while falling', function () {
object.setPosition(0, platform.getY() - object.getHeight() - 300);
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
true
);
}
object.setPosition(0, platform.getY() - object.getHeight());
for (let i = 0; i < 10; ++i) {
runtimeScene.renderAndStep(1000 / 60);
// Check the platformer object stays still.
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
false
);
expect(object.getBehavior('auto1').isMoving()).to.be(false);
}
});
it('can track object height changes', function () {
// Put the character near the right ledge of the platform.
object.setPosition(
platform.getX() + 10,
platform.getY() - object.getHeight() + 1
);
for (let i = 0; i < 15; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
false
);
expect(object.getX()).to.be(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// Make the platform under the character feet smaller.
object.setCustomWidthAndHeight(object.getWidth(), 9);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
false
);
// The character follows it.
expect(object.getY()).to.be(-19); // -19 = -10 (platform y) + -9 (object height)
// The character walks on the platform.
for (let i = 0; i < 10; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
false
);
}
expect(object.getY()).to.be(-19);
expect(object.getX()).to.be.above(16);
// Make the platform under the character feet bigger.
object.setCustomWidthAndHeight(object.getWidth(), 20);
runtimeScene.renderAndStep(1000 / 60);
// The character follows it.
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
});
it('can track platform angle changes', function () {
// The initial pltaforms AABB are put in RBush.
runtimeScene.renderAndStep(1000 / 60);
// Now change the angle to check that the AABB is updated in RBush.
platform.setAngle(90);
// Put the character above the rotated platform.
object.setPosition(
platform.getX() + platform.getWidth() / 2,
platform.getY() +
(platform.getHeight() - platform.getWidth()) / 2 -
object.getHeight() -
10
);
for (let i = 0; i < 15; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
// The character should land on it.
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
false
);
expect(object.getX()).to.be(30);
expect(object.getY()).to.be(-44);
});
});
});
describe(`(walk on flat floors with custom hitbox)`, function () {
let runtimeScene;
let object;
beforeEach(function () {
runtimeScene = makePlatformerTestRuntimeScene();
// Put a platformer object on a platform
object = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformerObjectBehavior',
name: 'auto1',
gravity: 1500,
maxFallingSpeed: 1500,
acceleration: 500,
deceleration: 1500,
maxSpeed: 500,
jumpSpeed: 900,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: 60,
jumpSustainTime: 0.2,
},
],
effects: [],
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: { x: 25, y: 25 },
centerPoint: { x: 50, y: 50 },
points: [
{ name: 'Center', x: 0, y: 0 },
{ name: 'Origin', x: 50, y: 50 },
],
hasCustomCollisionMask: true,
customCollisionMask: [
[
{ x: 25, y: 25 },
{ x: 75, y: 25 },
{ x: 75, y: 75 },
{ x: 25, y: 75 },
],
],
},
],
},
],
},
],
});
object.setUnscaledWidthAndHeight(100, 100);
object.setCustomWidthAndHeight(20, 40);
runtimeScene.addObject(object);
});
// The actual hitbox is 10x20.
const objectWidth = 10;
const objectHeight = 20;
const fall = (frameCount) => {
for (let i = 0; i < frameCount; ++i) {
const lastY = object.getY();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
true
);
expect(object.getBehavior('auto1').isMoving()).to.be(true);
expect(object.getY()).to.be.above(lastY);
}
};
const walkRight = (frameCount) => {
const behavior = object.getBehavior('auto1');
for (let i = 0; i < frameCount; ++i) {
const lastX = object.getX();
const lastSpeed = behavior.getCurrentSpeed();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isOnFloor()).to.be(true);
expect(object.getX()).to.be.above(lastX);
// Check that the object doesn't stop
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
}
};
const fallOnPlatform = (maxFrameCount) => {
// Ensure the object falls on the platform
for (let i = 0; i < maxFrameCount; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
//Check the object is on the platform
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isMoving()).to.be(false);
};
it('can walk on a platform and be blocked by a wall', function () {
// Put a platform.
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
// the 2nd platform is 2 pixels higher
const wall = addPlatformObject(runtimeScene);
wall.setPosition(
platform.getX() + platform.getWidth(),
// The platform is top is over the object
// and platform is bottom is under the object.
platform.getY() - wall.getHeight() + 5
);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
// walk right
for (let i = 0; i < 25; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
}
// is blocked by the wall
expect(object.getX()).to.be(wall.getX() - objectWidth);
expect(object.getY()).to.be(platform.getY() - objectHeight);
});
});
describe('Floating-point error mitigations', function () {
it('Specific coordinates with slopeMaxAngle=0 creating Y oscillations and drift on a moving floor', function () {
const runtimeScene = makePlatformerTestRuntimeScene();
// Create a Sprite object that has the origin at a specific position (see below)
// and that has a slope max angle of 0 (so it can't climb on a floor even if it's a bit higher
// than the bottom of the object).
const object = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformerObjectBehavior',
name: 'auto1',
gravity: 1300,
maxFallingSpeed: 1000,
acceleration: 500,
deceleration: 1500,
maxSpeed: 280,
jumpSpeed: 750,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: 0,
jumpSustainTime: 0.2,
},
],
effects: [],
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: { x: 5, y: 19 },
centerPoint: { x: 5, y: 46 },
points: [
{ name: 'Center', x: 5, y: 46 },
{ name: 'Origin', x: 5, y: 19 },
],
hasCustomCollisionMask: false,
},
],
},
],
},
],
});
// Set the size of the object so that it results in a specific
// Y position for the bottom of the object AABB:
object.setUnscaledWidthAndHeight(10, 92);
object.setCustomWidthAndHeight(10, 66.0008);
// Origin Y is originally 19.
// After the scaling, it is now 19*66.0008/92=13.6306.
// Set the Y position so that the object falls at a Y position on the floor
// that would generate oscillations.
object.setPosition(0, 139.3118);
runtimeScene.addObject(object);
// Put a platform at a specific Y that can cause oscillations.
const platform = addJumpThroughPlatformObject(runtimeScene);
platform.setPosition(0, 193.000000000001);
// This means that the exact Y position the object should take is:
// platform Y - height + origin Y = 193.000000000001-66.0008+13.6306 = 140.6298
// Wait for the object to fall on the floor
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
// Ensure it is on the floor
runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
// The Y position won't be exact because of floating point errors.
// expect(object.getY()).to.be(140.6298)
expect(object.getY()).to.be.within(140.6297999, 140.6298001);
// Move the platform by 6 pixels to the right.
platform.setX(platform.getX() + 1);
runtimeScene.renderAndStep(1000 / 60);
platform.setX(platform.getX() + 1);
runtimeScene.renderAndStep(1000 / 60);
platform.setX(platform.getX() + 1);
runtimeScene.renderAndStep(1000 / 60);
platform.setX(platform.getX() + 1);
runtimeScene.renderAndStep(1000 / 60);
platform.setX(platform.getX() + 1);
runtimeScene.renderAndStep(1000 / 60);
platform.setX(platform.getX() + 1);
runtimeScene.renderAndStep(1000 / 60);
// Ensure the object followed the platform on the X axis.
// If the floating point errors caused oscillations between two Y positions,
// it won't work because the object will get repositioned back to its old X position
// whenever the floor is considered "too high" for the object to reach.
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
expect(object.getY()).to.be.within(140.6297999, 140.6298001);
expect(object.getX()).to.be(6);
});
});
[20, 30, 60, 120].forEach((framesPerSecond) => {
describe(`(FPS independent trajectory: ${framesPerSecond} fps)`, function () {
let runtimeScene;
let object;
beforeEach(function () {
runtimeScene = makePlatformerTestRuntimeScene(1000 / framesPerSecond);
// Put a platformer object on a platform
object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformerObjectBehavior',
name: 'auto1',
gravity: 1500,
maxFallingSpeed: 1500,
acceleration: 500,
deceleration: 1500,
maxSpeed: 500,
jumpSpeed: 900,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: 60,
jumpSustainTime: 0.2,
useLegacyTrajectory: false,
},
],
effects: [],
});
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
});
const fallOnPlatform = (maxFrameCount) => {
// Ensure the object falls on the platform
for (let i = 0; i < maxFrameCount; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
//Check the object is on the platform
expect(object.getBehavior('auto1').isFalling()).to.be(false);
expect(object.getBehavior('auto1').isMoving()).to.be(false);
};
it('can walk', function () {
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
platform.setCustomWidthAndHeight(600, 32);
object.setPosition(0, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
expect(object.getY()).to.be(-30);
// Accelerate
for (let i = 0; i < framesPerSecond; ++i) {
object.getBehavior('auto1').simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
}
// Reached the maximum speed
expect(object.getX()).to.be.within(250 - epsilon, 250 + epsilon);
expect(object.getY()).to.be(platform.getY() - object.getHeight());
expect(object.getBehavior('auto1').getCurrentSpeed()).to.be.within(
500 - epsilon,
500 + epsilon
);
// Decelerate
for (let i = 0; i < framesPerSecond / 3; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
// Stopped
expect(object.getX()).to.be.within(333, 334);
expect(object.getY()).to.be(platform.getY() - object.getHeight());
expect(object.getBehavior('auto1').getCurrentSpeed()).to.be(0);
});
});
});
});

File diff suppressed because it is too large Load Diff

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