Compare commits

...

96 Commits

Author SHA1 Message Date
Clément Pasteau
0ffc88a470 Small fixes (#4116)
Do not show in changelog
2022-07-18 18:25:27 +02:00
Clément Pasteau
b389b3cc90 Fix onboarding flow for web (#4115) 2022-07-18 16:22:50 +02:00
Clément Pasteau
41a70ec1e7 Bump IDE version to 139 (#4113) 2022-07-18 12:26:07 +02:00
D8H
1844d826f9 [PathFinding] Add a warn for too long searches (#4112)
* Don't show in changelogs
2022-07-18 12:25:54 +02:00
github-actions[bot]
e7e45e9c9b Update translations [skip ci] (#4102)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2022-07-18 12:14:34 +02:00
Clément Pasteau
7f7dc0e07f Final fixes release 139 (#4111)
* Fix MultiAutoComplete resetting after each input
* Always open a scene when opening a project
2022-07-18 12:13:43 +02:00
D8H
7af0153ceb [Platformer] Fix: platformer characters can now jump when moving against a wall that is part of the same object as the floor. (#4106)
* This bug could happen when using a tilemap collision mask object and not sustaining the jump.
2022-07-18 12:07:15 +02:00
D8H
9aaabbcc1d Fix the box of collision masks in the editor (#4105)
* Don't show in changelogs
2022-07-18 10:14:56 +02:00
Florian Rival
55cc75e990 Update wording of games not visible on Liluo on the games dashboard
Don't show in changelog
2022-07-13 18:47:19 +02:00
AlexandreS
d3d6c50790 Fix custom tooltip crashing when payload is undefined (#4101)
Don't show in changelog
2022-07-13 18:04:05 +02:00
github-actions[bot]
c7f2e4312c Update translations [skip ci] (#4058)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2022-07-13 15:49:36 +02:00
Peter Anderson
38cff7bbce Fix typos: replace 'Musics' with 'Music' (#4099) 2022-07-13 15:36:27 +02:00
AlexandreS
761de213ac Fix tabs and links colors
Do not show in changelog
2022-07-13 15:14:05 +02:00
D8H
f871b64011 [TileMap] Collision mask object (#3313)
* The collision mask is read from the tile maps exported by Tiled 1.9+.
* Several collision masks can be used for instance to have platforms and ladders.
* Take a look to the wiki for more details https://wiki.gdevelop.io/gdevelop5/objects/tilemap
2022-07-13 15:12:12 +02:00
AlexandreS
9441d3a2d2 Adapt themes' colors for better contrast across the app 2022-07-13 11:31:36 +02:00
Florian Rival
e86348727e Add a button, in the Profile, to manage online the subscription to GDevelop services (#4096)
This allows to easily update a payment method or switch to another plan/cancel (though this can already be done from the interface)
2022-07-13 10:59:41 +02:00
Peter Anderson
754845e5cd Fix grammar for the description of the Platform behavior (#4098) 2022-07-13 10:49:50 +02:00
Fannie Yan
1b86c23b92 Replace leaderboards when a user opens a game template (#4094)
* Replace leaderboards when a user opens a game template
2022-07-12 15:33:55 +02:00
AlexandreS
3c44602486 Fix Physics2 behaviors (#4074)
* Make it possible to activate and reactivate physics 2 behaviour
* Fix behaviour for objects modified or destroyed during events life cycle
* Make Physics2 Collision condition valid in any step of behaviour life cycles
2022-07-12 14:46:49 +02:00
D8H
b0f1e962a4 Automatically select and rename new behaviors or functions in the extension editor (#4092) 2022-07-11 16:27:22 +02:00
D8H
c48c86a51f Make radian or degree explicit in descriptions for angles (#4091) 2022-07-11 14:47:21 +02:00
Florian Rival
2b83ec9871 Revert color changes in themes (#4089)
* Primary color is the one that is displaying well, with good contrast, on the background.
* Secondary is the "accent" one that works well for raised button, selected buttons, etc...

Don't show in changelog
2022-07-11 10:36:15 +02:00
Florian Rival
c340f8ad06 Fix display of games showcase
Don't show in changelog
2022-07-08 17:53:41 +02:00
Clément Pasteau
07341f1e00 Improve the new Homepage Learn section by categorising tutorials
* Tutorials are now organised into Full Games, Game Mechanics, and official videos from Beginner to Advanced!
2022-07-08 16:06:57 +02:00
Sebastian Krzyszkowiak
7402d4f29f Add an option in the Resources editor to preload audio files in cache without decoding (#4010)
* This is helpful to load the audio files at the loading of the game (so even if the internet connection is lost, they will be available in the "browser cache") but without decoding them in memory (which could make too much work for low end devices, resulting in crashes)
2022-07-07 12:56:32 +02:00
AlexandreS
8599a1cfa7 Allow to display up to 50 leaderboard entries in Liluo/in-game 2022-07-06 15:39:59 +02:00
Clément Pasteau
98bfc7a4e0 Revamp GDevelop Homepage
* New "Get Started" section, to guide newcomers on where to start with GDevelop
* New "Build" section, the entry point for your projects
* New "Learn" section, the entry point to tutorials and future help tools
* New "Play" section, the entry point for all games created with GDevelop
* New "Community" section, the entry point to stay informed with the GDevelop world
2022-07-06 14:00:46 +02:00
D8H
cef352276d Add a duplicate action in the functions lists contextual menu. (#4085) 2022-07-06 12:33:52 +02:00
Fannie Yan
49ea27fd54 Slightly rework games dashboard (#4084)
Don't show in changelog
2022-07-06 12:12:24 +02:00
D8H
23b5b16bdf Select copied element on behaviors and functions lists (#4083) 2022-07-06 11:06:12 +02:00
Arthur Pacaud
2124133b4a Add an option to wait for a network request action to end before running other actions (#4023)
* In the future, there will be other actions where you can optionally wait for their tasks to be finished before running the rest of the actions. It makes logic easier and more straightforward to express in events.
* This is similar to the Wait action, except that it's optional.
2022-07-05 23:58:08 +02:00
Florian Rival
905344f396 Fix contrast of expressions in Rosé Pine theme 2022-07-05 15:08:43 +02:00
Florian Rival
84b78dfe0f Order extensions according to what is specified in the extensions repository (#4080)
Don't show in changelog
2022-07-03 22:47:18 +02:00
Ehan
1d8086b2f4 Add a new "Rosé Pine" theme (Code Editor & UI Theme) (#4079)
* Based on https://rosepinetheme.com/ and adapted by @EhanAhamed for GDevelop.
2022-07-03 18:20:19 +02:00
Florian Rival
22d5d46601 Add a toggle to display community extensions (#4078)
* Community extensions are extensions made by a community member, but which are not reviewed by the GDevelop extension team.
  They can be useful, but also need to be carefully reviewed if you use some (inspect them or reach out to their author in case of doubt).
* If you want to submit an extension, see the [GitHub repository of community extensions](https://github.com/gdevelopapp/gdevelop-extensions).
2022-07-02 13:36:37 +02:00
Fannie Yan
a5d56c37c9 Allow user to manage builds (#4075)
* In build tab:
  - Allow users to edit build names
  - Allow users to delete builds
* In feedback tab:
  - Display build name if a feedback is linked to one and allow users to filter feedbacks on builds
2022-07-01 10:44:29 +02:00
Florian Rival
5025ad9fb9 Avoid bad expression warnings in the console when using force actions without changing the force type (#4073) 2022-06-30 19:20:32 +02:00
D8H
daae78e802 Add version locks for rechart. (#4076)
Don't show in changelogs.
2022-06-30 16:28:02 +02:00
Florian Rival
9ee4b704da Clean more deprecated include files in extensions (#4072)
Don't show in changelog
2022-06-30 12:09:17 +02:00
D8H
a8991b7c9b Add game analytics charts (#4046)
The game dash board shows new charts to understand how well a game is doing on several aspects:
* the popularity (number of players)
* the player engagement (how long the game is played)

Note that players data are anonymized.
2022-06-30 12:06:03 +02:00
Florian Rival
cf061638b8 Avoid warnings in the console during preview/export by cleaning old C++ include files (#4070)
Only show in developer changelog
2022-06-28 19:26:25 +02:00
Arthur Pacaud
0686e02e27 Don't show objects already in a group in the selector to add a new object to this group (#4069) 2022-06-28 10:52:46 +02:00
Arthur Pacaud
e65b576e4b Adding actions to tween the value of a scene variable or a layer camera position (#4022)
* Like tweens added on objects, these tweens can be manipulated: paused, resumed or stopped.
2022-06-27 23:56:16 +02:00
Florian Rival
aacfed02a1 Allow the web-app to use relative resources if found when the project is opened from a public URL (#4066)
* For example, this can be useful to open a desktop project stored on a public space like GitHub (using the "raw" raw.githubusercontent.com/... urls given by GitHub)

Only show in developer changelog
2022-06-27 15:59:13 +02:00
Fannie Yan
6a25a9ad77 Allow user to open their games for feedbacks (#4045)
* Rework slighlty the game dashboard to allow users to make their game discoverable on Liluo.io, add a banner asking for feedback on their game pages on Liluo.io and add a banner asking for feedback on their build pages
* Add a tab Feedback when managing games, accessible through the games dashboard that allows users to see and process the feedbacks given to their game.
2022-06-27 15:23:43 +02:00
Clément Pasteau
e1140609d0 Add a button to start (or redo) the onboarding on the homepage
* Also add new languages FR, PT & ES
2022-06-27 15:18:15 +02:00
AlexandreS
81ef11163d Bump newIDE version 2022-06-21 16:41:38 +02:00
AlexandreS
fcc19a6dcf Fix collision bug introduced when fixing a collision bug 2022-06-21 16:41:18 +02:00
github-actions[bot]
bcad2d5667 Update translations [skip ci] (#4042)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2022-06-21 16:01:54 +02:00
D8H
3c83e5d24a No longer count pauses (when the tab is hidden) for the players session duration statistics (#4054) 2022-06-21 15:04:41 +02:00
Florian Rival
d07088900f Bump newIDE version 2022-06-16 18:28:13 +02:00
github-actions[bot]
fcb0c27e23 Update translations [skip ci] (#4030) 2022-06-16 17:06:43 +02:00
Fannie Yan
a6ee1c3e1c Allow more freedom on horizontal movements in events sheet (#4037)
* Allow more freedom on horizontal movements in events sheet
2022-06-16 11:09:26 +02:00
D8H
5669eae198 [Physics] Add mass and moment of inertia expressions (#4000) 2022-06-16 10:29:28 +02:00
AlexandreS
8f97a5ba69 Fix: Detect Physics2 contacts between objects that happen between frames 2022-06-16 10:12:15 +02:00
D8H
d57a755b2f Extract the analytics panel from the game detail dialog (#4038)
* Don't show in changelogs
2022-06-15 19:33:14 +02:00
Fannie Yan
5118421de7 Suggest all expression types in Text fields (#4033)
* Suggest all expressions in autocompletion suggestion and in expression selector for Text parameter fields
* Automatically convert number expressions to Text (using ToString()) when needed
2022-06-15 15:58:06 +02:00
Arthur Pacaud
ff15a37da7 Add WebManifest to exported web games (#4021)
* This allows a game hosted on a website to have a proper icon and orientation when a shortcut to it is added to the home screen on Android or iOS
2022-06-14 15:15:35 +02:00
Florian Rival
beac19089f Upgrade gh-pages to avoid history of the published web-app to take useless space
* history option was not working previously

Don't show in changelog
2022-06-13 15:45:37 +02:00
Florian Rival
e86e4ef9f5 Update all dark themes and rework the light theme to use the modern look'n'feel (#4028) 2022-06-13 12:54:10 +02:00
github-actions[bot]
693a2dbd2c Update translations [skip ci] (#4006)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2022-06-13 12:32:11 +02:00
AlexandreS
23318c2c28 Fix: Remove clicks on checkboxes that are far from the label on the same line 2022-06-13 10:55:11 +02:00
Clément Pasteau
3e811f1b9d Fix asset thumbnail sometimes having a wrong size when navigating the Asset store 2022-06-13 10:26:46 +02:00
Florian Rival
9ea6c034c3 Fix category of "Error of last save attempt" leaderboard expression 2022-06-12 17:47:24 +02:00
Sebastian Krzyszkowiak
b0ac0b1254 Fix music files staying muted after loading after upgrading to Howler.js 2.2.3 (#4011)
* This is caused by a bug in recent versions of Howler.js. Since there's no fix in Howler yet, this change reverts the problematic commit (0323af9b84) on top of v2.2.3 release.
* See https://github.com/goldfire/howler.js/issues/1603 for more details.

Only show in developer changelog
2022-06-09 10:19:42 +02:00
Florian Rival
e8b34ca535 Fix camera actions/conditions/expressions broken since introducing new expressions (#4012)
Removed some logs so that it's easier to spot errors reported.

Don't show in changelog
2022-06-09 10:09:14 +02:00
Clément Pasteau
70226f45aa Rework Asset Pack add to a list of filtered assets
Do not show in changelog
2022-06-08 17:10:58 +02:00
Clément Pasteau
b1572102c7 Bump IDE to 5.0.136
Do not show in changelog
2022-06-08 15:10:49 +02:00
Sebastian Krzyszkowiak
63da874469 Preload sounds sequentially to avoid overloading the browser
- Trying to preload all sounds at once can overwhelm the browser
in case of projects with many big sound files. Load the files
sequentially, the same way images are being preloaded.
2022-06-08 15:02:54 +02:00
Sebastian Krzyszkowiak
0963f2b8f6 Fix sounds being looped correctly on Firefox
- Upgrading Howler.js to v2.2.3 fixes sounds not being looped under recent Firefox versions (see https://github.com/goldfire/howler.js/issues/1442)
2022-06-08 15:00:01 +02:00
github-actions[bot]
5aa4fd5739 Update translations [skip ci] (#3924) 2022-06-08 14:57:29 +02:00
Oxey405
11c0248df5 Added an option to remove all unused resources 2022-06-08 14:53:11 +02:00
Fannie Yan
5bdc9769df Add camera borders expressions and conditions (#3999)
* Add CameraBorderLeft, CameraBorderRight, CameraBorderTop and CameraBorderBottom expressions and conditions
* Add CameraCenterX and CameraCenterY expressions and hide CameraX and CameraY expressions
2022-06-08 10:01:45 +02:00
Clément Pasteau
998ea7aa40 improve small things for Asset store and Events drag and drop (#3993)
* Hide Cancel action while installing

* Reset Pack Install disabled status on homepage

* Remove pointer from SelectField

* Create Chip component to harmonize cursor behavior to default

* Fix InlineCheckbox random cursors

* Improve drop indicator style

* Fix chip delete icon hover cursor

* Change drop indicator for multiple themes to be more visible
2022-06-07 10:32:42 +02:00
Fannie Yan
124003b4ac Save template slug in project (#3988)
only show in developer changelog
2022-06-07 10:26:30 +02:00
Florian Rival
96435530cd Set search bars to be rounded in the modern theme (#3994)
* Search bar have rounding and margins in most dialogs
* In lists, they stay "integrated" (no rounding, no margins)
* Also fix an icon that was too big

Don't show in changelog
2022-06-06 14:42:34 +02:00
D8H
8013ff0d7d Show the tag name as the asset page title (#3992)
Don't show in changelog
2022-06-06 10:43:59 +02:00
D8H
763aa0aa25 Show expression errors directly in the event sheet (#3823) 2022-06-03 23:02:51 +02:00
D8H
46dfb57859 Allow to go back to previous asset pages (#3986) 2022-06-03 19:16:35 +02:00
Fannie Yan
186eb4fcf0 Fix undo and redo in events sheet (#3981)
* Scroll to last change
* Select all events where a change occured
2022-06-03 16:28:27 +02:00
Florian Rival
c78c5def05 Add support for displaying external (optionally, paid) asset packs in the Asset Store (#3987)
* If you're an author of a paid asset pack, feel free to reach out to the GDevelop team on Discord or on the forum to get your pack listed!
2022-06-03 16:20:08 +02:00
Clément Pasteau
d694458fae Update all dialogs to have a way to close, except the ones with significant change (#3985) 2022-06-03 15:20:21 +02:00
D8H
74d6e51a2d Give asset suggestions after selecting an asset in the store 2022-06-03 15:12:31 +02:00
AlexandreS
ca5c3a27b0 Fix leaderboard reset action that used to timeout 2022-06-03 11:05:27 +02:00
Clément Pasteau
9ccdd2a0fa Give the possibility to add an asset pack as a whole
* New Dialog indicating how many assets are already in the project
* Option to add only the remaining assets
* Improved design/UX overall
2022-06-02 18:28:37 +02:00
D8H
9ec7fc71a8 The asset store remembers the opened page (#3982) 2022-06-02 12:41:53 +02:00
D8H
0f0a4034b9 Fix the hook dependency of the search. (#3980)
Don't show in changelog
2022-06-01 22:43:03 +02:00
D8H
6f831fe471 Cleanup filters context (#3979)
Don't show in changelogs.
2022-06-01 19:21:27 +02:00
D8H
0403882a18 Keep assets from a pack in order (#3978) 2022-06-01 18:30:21 +02:00
Florian Rival
318b1a62c9 Display the modern Dark theme by default for new users 2022-06-01 17:04:07 +02:00
Aurélien Vivet
41c9069289 Disable the autocomplete for Text Input objects (#3895) 2022-06-01 16:56:19 +02:00
Florian Rival
e98c96b5a4 Add a new "Modern" dark theme (#3974)
* This theme brings some changes to make the interface more readable to new users (outlined buttons) and a more modern approach (vibrant color, rounded buttons). It's still based on Material Design and similar to other themes, but is a first step toward an improved interface.
2022-06-01 14:36:07 +02:00
AlexandreS
893013e102 Fix loss of data when editing variables from the instance properties panel 2022-06-01 11:58:32 +02:00
D8H
dc41ec862c Sort asset search results and make the color filter not excluding. (#3965) 2022-06-01 10:39:10 +02:00
Florian Rival
5495257732 Reduce size of events in JSON by not writing empty events/sub-instructions arrays (#3975)
Only show in developer changelog
2022-05-31 17:24:01 +02:00
D8H
8c0a3221de Update the mouse buttons status when the cursor comes back inside the game view (#3970) 2022-05-30 13:35:55 +02:00
686 changed files with 35937 additions and 10178 deletions

View File

@@ -23,8 +23,9 @@ vector<gd::String> CommentEvent::GetAllSearchableStrings() const {
bool CommentEvent::ReplaceAllSearchableStrings(
std::vector<gd::String> newSearchableString) {
if (newSearchableString[0] == com1) return false;
SetComment(newSearchableString[0]);
return newSearchableString[0] == com1;
return true;
}
void CommentEvent::SerializeTo(SerializerElement &element) const {

View File

@@ -53,8 +53,10 @@ void ForEachChildVariableEvent::SerializeTo(SerializerElement& element) const {
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(
actions, element.AddChild("actions"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
if (!events.IsEmpty())
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
}
void ForEachChildVariableEvent::UnserializeFrom(gd::Project& project,
@@ -66,8 +68,12 @@ void ForEachChildVariableEvent::UnserializeFrom(gd::Project& project,
project, conditions, element.GetChild("conditions", 0, "Conditions"));
gd::EventsListSerialization::UnserializeInstructionsFrom(
project, actions, element.GetChild("actions", 0, "Actions"));
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
events.Clear();
if (element.HasChild("events", "Events")) {
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
}
}
} // namespace gd

View File

@@ -74,8 +74,10 @@ void ForEachEvent::SerializeTo(SerializerElement& element) const {
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(
actions, element.AddChild("actions"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
if (!events.IsEmpty())
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
}
void ForEachEvent::UnserializeFrom(gd::Project& project,
@@ -86,8 +88,12 @@ void ForEachEvent::UnserializeFrom(gd::Project& project,
project, conditions, element.GetChild("conditions", 0, "Conditions"));
gd::EventsListSerialization::UnserializeInstructionsFrom(
project, actions, element.GetChild("actions", 0, "Actions"));
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
events.Clear();
if (element.HasChild("events", "Events")) {
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
}
}
} // namespace gd

View File

@@ -29,8 +29,9 @@ vector<gd::String> GroupEvent::GetAllSearchableStrings() const {
bool GroupEvent::ReplaceAllSearchableStrings(
std::vector<gd::String> newSearchableString) {
if (newSearchableString[0] == name) return false;
SetName(newSearchableString[0]);
return newSearchableString[0] == name;
return true;
}
void GroupEvent::SerializeTo(SerializerElement& element) const {

View File

@@ -75,8 +75,10 @@ void RepeatEvent::SerializeTo(SerializerElement& element) const {
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(
actions, element.AddChild("actions"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
if (!events.IsEmpty())
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
}
void RepeatEvent::UnserializeFrom(gd::Project& project,
@@ -89,8 +91,12 @@ void RepeatEvent::UnserializeFrom(gd::Project& project,
project, conditions, element.GetChild("conditions", 0, "Conditions"));
gd::EventsListSerialization::UnserializeInstructionsFrom(
project, actions, element.GetChild("actions", 0, "Actions"));
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
events.Clear();
if (element.HasChild("events", "Events")) {
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
}
}
} // namespace gd

View File

@@ -53,8 +53,10 @@ void StandardEvent::SerializeTo(SerializerElement& element) const {
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(
actions, element.AddChild("actions"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
if (!events.IsEmpty())
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
}
void StandardEvent::UnserializeFrom(gd::Project& project,

View File

@@ -52,8 +52,10 @@ void WhileEvent::SerializeTo(SerializerElement& element) const {
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(
actions, element.AddChild("actions"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
if (!events.IsEmpty())
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
}
void WhileEvent::UnserializeFrom(gd::Project& project,
@@ -68,8 +70,12 @@ void WhileEvent::UnserializeFrom(gd::Project& project,
project, conditions, element.GetChild("conditions", 0, "Conditions"));
gd::EventsListSerialization::UnserializeInstructionsFrom(
project, actions, element.GetChild("actions", 0, "Actions"));
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
events.Clear();
if (element.HasChild("events", "Events")) {
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
}
}
} // namespace gd

View File

@@ -468,6 +468,14 @@ gd::String EventsCodeGenerator::GenerateActionCode(
action, *this, context);
}
// Get the correct function name depending on whether it should be async or
// not.
const gd::String& functionCallName =
instrInfos.IsAsync() &&
(!instrInfos.IsOptionallyAsync() || action.IsAwaited())
? instrInfos.codeExtraInformation.asyncFunctionCallName
: instrInfos.codeExtraInformation.functionCallName;
// Be sure there is no lack of parameter.
while (action.GetParameters().size() < instrInfos.parameters.size()) {
vector<gd::Expression> parameters = action.GetParameters();
@@ -522,6 +530,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
@@ -556,6 +565,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
GenerateBehaviorAction(realObjects[i],
action.GetParameter(1).GetPlainString(),
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
@@ -567,8 +577,11 @@ gd::String EventsCodeGenerator::GenerateActionCode(
} else {
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode +=
GenerateFreeAction(arguments, instrInfos, context, optionalAsyncCallbackName);
actionCode += GenerateFreeAction(functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
}
return actionCode;
@@ -658,7 +671,7 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
}
gd::String EventsCodeGenerator::GenerateParameterCodes(
const gd::String& parameter,
const gd::Expression& parameter,
const gd::ParameterMetadata& metadata,
gd::EventsCodeGenerationContext& context,
const gd::String& lastObjectName,
@@ -668,19 +681,21 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
if (ParameterMetadata::IsExpression("number", metadata.type)) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "number", parameter);
*this, context, "number", parameter, lastObjectName);
} else if (ParameterMetadata::IsExpression("string", metadata.type)) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "string", parameter);
*this, context, "string", parameter, lastObjectName);
} else if (ParameterMetadata::IsExpression("variable", metadata.type)) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, metadata.type, parameter, lastObjectName);
} else if (ParameterMetadata::IsObject(metadata.type)) {
// It would be possible to run a gd::ExpressionCodeGenerator if later
// objects can have nested objects, or function returning objects.
argOutput = GenerateObject(parameter, metadata.type, context);
argOutput =
GenerateObject(parameter.GetPlainString(), metadata.type, context);
} else if (metadata.type == "relationalOperator") {
argOutput += parameter == "=" ? "==" : parameter;
auto parameterString = parameter.GetPlainString();
argOutput += parameterString == "=" ? "==" : parameterString;
if (argOutput != "==" && argOutput != "<" && argOutput != ">" &&
argOutput != "<=" && argOutput != ">=" && argOutput != "!=") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
@@ -689,7 +704,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
argOutput = "\"" + argOutput + "\"";
} else if (metadata.type == "operator") {
argOutput += parameter;
argOutput += parameter.GetPlainString();
if (argOutput != "=" && argOutput != "+" && argOutput != "-" &&
argOutput != "/" && argOutput != "*") {
cout << "Warning: Bad operator: Set to = by default." << endl;
@@ -698,9 +713,9 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
argOutput = "\"" + argOutput + "\"";
} else if (ParameterMetadata::IsBehavior(metadata.type)) {
argOutput = GenerateGetBehaviorNameCode(parameter);
argOutput = GenerateGetBehaviorNameCode(parameter.GetPlainString());
} else if (metadata.type == "key") {
argOutput = "\"" + ConvertToString(parameter) + "\"";
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.type == "audioResource" ||
metadata.type == "bitmapFontResource" ||
metadata.type == "fontResource" ||
@@ -710,16 +725,20 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
// Deprecated, old parameter names:
metadata.type == "password" || metadata.type == "musicfile" ||
metadata.type == "soundfile" || metadata.type == "police") {
argOutput = "\"" + ConvertToString(parameter) + "\"";
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.type == "mouse") {
argOutput = "\"" + ConvertToString(parameter) + "\"";
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.type == "yesorno") {
argOutput += (parameter == "yes" || parameter == "oui") ? GenerateTrue()
: GenerateFalse();
auto parameterString = parameter.GetPlainString();
argOutput += (parameterString == "yes" || parameterString == "oui")
? GenerateTrue()
: GenerateFalse();
} else if (metadata.type == "trueorfalse") {
auto parameterString = parameter.GetPlainString();
// This is duplicated in AdvancedExtension.cpp for GDJS
argOutput += (parameter == "True" || parameter == "Vrai") ? GenerateTrue()
: GenerateFalse();
argOutput += (parameterString == "True" || parameterString == "Vrai")
? GenerateTrue()
: GenerateFalse();
}
// Code only parameter type
else if (metadata.type == "inlineCode") {
@@ -738,7 +757,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
if (!metadata.type.empty())
cout << "Warning: Unknown type of parameter \"" << metadata.type
<< "\"." << std::endl;
argOutput += "\"" + ConvertToString(parameter) + "\"";
argOutput += "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
}
}
@@ -758,7 +777,7 @@ vector<gd::String> EventsCodeGenerator::GenerateParametersCodes(
parametersInfo,
[this, &context, &supplementaryParametersTypes, &arguments](
const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
const gd::String& lastObjectName) {
gd::String argOutput =
GenerateParameterCodes(parameterValue,
@@ -1079,6 +1098,7 @@ gd::String EventsCodeGenerator::GenerateBehaviorCondition(
}
gd::String EventsCodeGenerator::GenerateFreeAction(
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
@@ -1092,21 +1112,21 @@ gd::String EventsCodeGenerator::GenerateFreeAction(
call = GenerateOperatorCall(
instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName,
functionCallName,
instrInfos.codeExtraInformation.optionalAssociatedInstruction);
else if (instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::Mutators)
call =
GenerateMutatorCall(instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName);
functionCallName);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName);
functionCallName);
} else {
call = instrInfos.codeExtraInformation.functionCallName + "(" +
call = functionCallName + "(" +
GenerateArgumentsList(arguments) + ")";
}
@@ -1120,6 +1140,7 @@ gd::String EventsCodeGenerator::GenerateFreeAction(
gd::String EventsCodeGenerator::GenerateObjectAction(
const gd::String& objectName,
const gd::ObjectMetadata& objInfo,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
@@ -1133,27 +1154,25 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
call = GenerateOperatorCall(
instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName,
functionCallName,
instrInfos.codeExtraInformation.optionalAssociatedInstruction,
2);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName,
2);
instrInfos, arguments, functionCallName, 2);
return "For each picked object \"" + objectName + "\", call " + call +
".\n";
} else {
gd::String argumentsStr = GenerateArgumentsList(arguments, 1);
call = instrInfos.codeExtraInformation.functionCallName + "(" +
argumentsStr + ")";
call = functionCallName + "(" + argumentsStr + ")";
return "For each picked object \"" + objectName + "\", call " + call + "(" +
argumentsStr + ")" +
(optionalAsyncCallbackName.empty() ? "" : (", then call" + optionalAsyncCallbackName)) +
(optionalAsyncCallbackName.empty()
? ""
: (", then call" + optionalAsyncCallbackName)) +
".\n";
}
}
@@ -1162,6 +1181,7 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
const gd::String& objectName,
const gd::String& behaviorName,
const gd::BehaviorMetadata& autoInfo,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
@@ -1175,26 +1195,28 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
call = GenerateOperatorCall(
instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName,
functionCallName,
instrInfos.codeExtraInformation.optionalAssociatedInstruction,
2);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
instrInfos.codeExtraInformation.functionCallName,
functionCallName,
2);
return "For each picked object \"" + objectName + "\", call " + call +
" for behavior \"" + behaviorName + "\".\n";
} else {
gd::String argumentsStr = GenerateArgumentsList(arguments, 2);
call = instrInfos.codeExtraInformation.functionCallName + "(" +
call = functionCallName + "(" +
argumentsStr + ")";
return "For each picked object \"" + objectName + "\", call " + call + "(" +
argumentsStr + ")" + " for behavior \"" + behaviorName + "\"" +
(optionalAsyncCallbackName.empty() ? "" : (", then call" + optionalAsyncCallbackName)) +
(optionalAsyncCallbackName.empty()
? ""
: (", then call" + optionalAsyncCallbackName)) +
".\n";
}
}
@@ -1243,7 +1265,7 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
return argumentsStr;
}
EventsCodeGenerator::EventsCodeGenerator(gd::Project& project_,
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_)
: platform(platform_),
@@ -1260,7 +1282,7 @@ EventsCodeGenerator::EventsCodeGenerator(gd::Project& project_,
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& objectsAndGroups_)
: platform(platform_),
globalObjectsAndGroups(globalObjectsAndGroups_),

View File

@@ -48,7 +48,7 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Construct a code generator for the specified
* platform/project/layout.
*/
EventsCodeGenerator(gd::Project& project_,
EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_);
@@ -57,7 +57,7 @@ class GD_CORE_API EventsCodeGenerator {
* objects/groups and platform
*/
EventsCodeGenerator(const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& objectsAndGroups_);
virtual ~EventsCodeGenerator(){};
@@ -154,9 +154,10 @@ class GD_CORE_API EventsCodeGenerator {
* \param context Context used for generation
* \return Code
*/
gd::String GenerateActionCode(gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
gd::String GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
struct CallbackDescriptor {
CallbackDescriptor(const gd::String functionName_,
@@ -174,7 +175,8 @@ class GD_CORE_API EventsCodeGenerator {
*/
const gd::String argumentsList;
/**
* A set of all objects that need to be backed up to be passed to the callback code.
* A set of all objects that need to be backed up to be passed to the
* callback code.
*/
const std::set<gd::String> requiredObjects;
};
@@ -327,7 +329,7 @@ class GD_CORE_API EventsCodeGenerator {
/**
* \brief Get the global objects/groups used for code generation.
*/
gd::ObjectsContainer& GetGlobalObjectsAndGroups() const {
const gd::ObjectsContainer& GetGlobalObjectsAndGroups() const {
return globalObjectsAndGroups;
}
@@ -348,7 +350,7 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Get the project the code is being generated for.
* \warning This is only valid if HasProjectAndLayout() is true.
*/
gd::Project& GetProject() const { return *project; }
const gd::Project& GetProject() const { return *project; }
/**
* \brief Get the layout the code is being generated for.
@@ -507,17 +509,17 @@ class GD_CORE_API EventsCodeGenerator {
* - currentScene: Reference to the current runtime scene.
* - objectList : a map containing lists of objects which are specified by the
object name in another parameter.
* - objectListOrEmptyIfJustDeclared : Same as `objectList` but do not pick object if
they are not already picked.
* - objectPtr: Return a reference to the object specified by the object name in
another parameter. Example:
* - objectListOrEmptyIfJustDeclared : Same as `objectList` but do not pick
object if they are not already picked.
* - objectPtr: Return a reference to the object specified by the object name
in another parameter. Example:
* \code
.AddParameter("object", _("Object"))
.AddParameter("objectPtr", _("Target object"))
* \endcode
*/
virtual gd::String GenerateParameterCodes(
const gd::String& parameter,
const gd::Expression& parameter,
const gd::ParameterMetadata& metadata,
gd::EventsCodeGenerationContext& context,
const gd::String& lastObjectName,
@@ -700,6 +702,7 @@ class GD_CORE_API EventsCodeGenerator {
gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateFreeAction(
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
@@ -708,6 +711,7 @@ class GD_CORE_API EventsCodeGenerator {
virtual gd::String GenerateObjectAction(
const gd::String& objectName,
const gd::ObjectMetadata& objInfo,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
@@ -717,6 +721,7 @@ class GD_CORE_API EventsCodeGenerator {
const gd::String& objectName,
const gd::String& behaviorName,
const gd::BehaviorMetadata& autoInfo,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
@@ -770,13 +775,13 @@ class GD_CORE_API EventsCodeGenerator {
const gd::Platform& platform; ///< The platform being used.
gd::ObjectsContainer& globalObjectsAndGroups;
const gd::ObjectsContainer& globalObjectsAndGroups;
const gd::ObjectsContainer& objectsAndGroups;
bool hasProjectAndLayout; ///< true only if project and layout are valid
///< references. If false, they should not be used.
gd::Project* project; ///< The project being used.
const gd::Layout* scene; ///< The scene being generated.
const gd::Project* project; ///< The project being used.
const gd::Layout* scene; ///< The scene being generated.
bool errorOccurred; ///< Must be set to true if an error occured.
bool compilationForRuntime; ///< Is set to true if the code generation is

View File

@@ -25,36 +25,38 @@
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
namespace gd {
gd::String ExpressionCodeGenerator::GenerateExpressionCode(
EventsCodeGenerator& codeGenerator,
EventsCodeGenerationContext& context,
const gd::String& type,
const gd::String& expression,
const gd::String& objectName) {
gd::ExpressionParser2 parser(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups());
ExpressionCodeGenerator generator(codeGenerator, context);
const gd::String& rootType,
const gd::Expression& expression,
const gd::String& rootObjectName) {
ExpressionCodeGenerator generator(rootType, rootObjectName, codeGenerator, context);
auto node = parser.ParseExpression(type, expression, objectName);
auto node = expression.GetRootNode();
if (!node) {
std::cout << "Error: error while parsing: \"" << expression << "\" ("
<< type << ")" << std::endl;
std::cout << "Error: error while parsing: \"" << expression.GetPlainString()
<< "\" (" << rootType << ")" << std::endl;
return generator.GenerateDefaultValue(type);
return generator.GenerateDefaultValue(rootType);
}
gd::ExpressionValidator validator;
gd::ExpressionValidator validator(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootType);
node->Visit(validator);
if (!validator.GetErrors().empty()) {
std::cout << "Error: \"" << validator.GetErrors()[0]->GetMessage()
<< "\" in: \"" << expression << "\" (" << type << ")"
<< std::endl;
<< "\" in: \"" << expression.GetPlainString() << "\" ("
<< rootType << ")" << std::endl;
return generator.GenerateDefaultValue(type);
return generator.GenerateDefaultValue(rootType);
}
node->Visit(generator);
@@ -97,15 +99,24 @@ void ExpressionCodeGenerator::OnVisitTextNode(TextNode& node) {
void ExpressionCodeGenerator::OnVisitVariableNode(VariableNode& node) {
// This "translation" from the type to an enum could be avoided
// if all types were moved to an enum.
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootType,
node);
EventsCodeGenerator::VariableScope scope =
node.type == "globalvar"
type == "globalvar"
? gd::EventsCodeGenerator::PROJECT_VARIABLE
: ((node.type == "scenevar")
: ((type == "scenevar")
? gd::EventsCodeGenerator::LAYOUT_VARIABLE
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
node.name, scope, context, node.objectName);
node.name, scope, context, objectName);
if (node.child) node.child->Visit(*this);
}
@@ -117,7 +128,7 @@ void ExpressionCodeGenerator::OnVisitVariableAccessorNode(
void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) {
ExpressionCodeGenerator generator(codeGenerator, context);
ExpressionCodeGenerator generator("string", "", codeGenerator, context);
node.expression->Visit(generator);
output +=
codeGenerator.GenerateVariableBracketAccessor(generator.GetOutput());
@@ -125,39 +136,79 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
}
void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
if (gd::ParameterMetadata::IsObject(node.type)) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootType,
node);
if (gd::ParameterMetadata::IsObject(type)) {
output +=
codeGenerator.GenerateObject(node.identifierName, node.type, context);
} else {
codeGenerator.GenerateObject(node.identifierName, type, context);
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
EventsCodeGenerator::VariableScope scope =
type == "globalvar"
? gd::EventsCodeGenerator::PROJECT_VARIABLE
: ((type == "scenevar")
? gd::EventsCodeGenerator::LAYOUT_VARIABLE
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
node.identifierName, scope, context, objectName);
if (!node.childIdentifierName.empty()) {
output += codeGenerator.GenerateVariableAccessor(node.childIdentifierName);
}
} else if (node.childIdentifierName.empty()) {
output += "/* Error during generation, unrecognized identifier type: " +
codeGenerator.ConvertToString(node.type) + " with value " +
codeGenerator.ConvertToString(type) + " with value " +
codeGenerator.ConvertToString(node.identifierName) + " */ " +
codeGenerator.ConvertToStringExplicit(node.identifierName);
}
else {
// This is for function names that are put in IdentifierNode
// because the type is needed to tell them appart from variables.
output += GenerateDefaultValue(type);
}
}
void ExpressionCodeGenerator::OnVisitFunctionCallNode(FunctionCallNode& node) {
if (gd::MetadataProvider::IsBadExpressionMetadata(node.expressionMetadata)) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootType,
node);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
output += "/* Error during generation, function not found: " +
codeGenerator.ConvertToString(node.functionName) + " */ " +
GenerateDefaultValue(node.type);
GenerateDefaultValue(type);
return;
}
if (!node.objectName.empty()) {
if (!node.behaviorName.empty()) {
output += GenerateBehaviorFunctionCode(node.type,
output += GenerateBehaviorFunctionCode(type,
node.objectName,
node.behaviorName,
node.parameters,
node.expressionMetadata);
metadata);
} else {
output += GenerateObjectFunctionCode(
node.type, node.objectName, node.parameters, node.expressionMetadata);
type, node.objectName, node.parameters, metadata);
}
} else {
output +=
GenerateFreeFunctionCode(node.parameters, node.expressionMetadata);
GenerateFreeFunctionCode(node.parameters, metadata);
}
}
@@ -305,18 +356,21 @@ gd::String ExpressionCodeGenerator::GenerateParametersCodes(
auto& parameterMetadata = expressionMetadata.parameters[i];
if (!parameterMetadata.IsCodeOnly()) {
ExpressionCodeGenerator generator(codeGenerator, context);
if (nonCodeOnlyParameterIndex < parameters.size()) {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootObjectName,
*parameters[nonCodeOnlyParameterIndex].get());
ExpressionCodeGenerator generator(parameterMetadata.GetType(), objectName, codeGenerator, context);
parameters[nonCodeOnlyParameterIndex]->Visit(generator);
parametersCode += generator.GetOutput();
} else if (parameterMetadata.IsOptional()) {
ExpressionCodeGenerator generator(parameterMetadata.GetType(), "", codeGenerator, context);
// Optional parameters default value were not parsed at the time of the
// expression parsing. Parse them now.
ExpressionParser2 parser(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups());
auto node = parser.ParseExpression(parameterMetadata.GetType(),
parameterMetadata.GetDefaultValue());
ExpressionParser2 parser;
auto node = parser.ParseExpression(parameterMetadata.GetDefaultValue());
node->Visit(generator);
parametersCode += generator.GetOutput();
@@ -374,12 +428,22 @@ gd::String ExpressionCodeGenerator::GenerateDefaultValue(
}
void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
output += GenerateDefaultValue(node.type);
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootType,
node);
output += GenerateDefaultValue(type);
}
void ExpressionCodeGenerator::OnVisitObjectFunctionNameNode(
ObjectFunctionNameNode& node) {
output += GenerateDefaultValue(node.type);
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootType,
node);
output += GenerateDefaultValue(type);
}
} // namespace gd

View File

@@ -9,7 +9,6 @@
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/String.h"
@@ -35,9 +34,11 @@ namespace gd {
*/
class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
public:
ExpressionCodeGenerator(EventsCodeGenerator& codeGenerator_,
ExpressionCodeGenerator(const gd::String &rootType_,
const gd::String &rootObjectName_,
EventsCodeGenerator& codeGenerator_,
EventsCodeGenerationContext& context_)
: codeGenerator(codeGenerator_), context(context_){};
: rootType(rootType_), rootObjectName(rootObjectName_), codeGenerator(codeGenerator_), context(context_){};
virtual ~ExpressionCodeGenerator(){};
/**
@@ -57,7 +58,7 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
static gd::String GenerateExpressionCode(EventsCodeGenerator& codeGenerator,
EventsCodeGenerationContext& context,
const gd::String& type,
const gd::String& expression,
const gd::Expression& expression,
const gd::String& objectName = "");
const gd::String& GetOutput() { return output; };
@@ -103,6 +104,8 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
gd::String output;
EventsCodeGenerator& codeGenerator;
EventsCodeGenerationContext& context;
const gd::String rootType;
const gd::String rootObjectName;
};
} // namespace gd

View File

@@ -75,7 +75,8 @@ void BaseEvent::PreprocessAsyncActions(const gd::Platform& platform) {
const auto& action = actionsList->at(aId);
const gd::InstructionMetadata& actionMetadata =
gd::MetadataProvider::GetActionMetadata(platform, action.GetType());
if (actionMetadata.IsAsync()) {
if (actionMetadata.IsAsync() &&
(!actionMetadata.IsOptionallyAsync() || action.IsAwaited())) {
gd::InstructionsList remainingActions;
remainingActions.InsertInstructions(
*actionsList, aId + 1, actionsList->size() - 1);

View File

@@ -0,0 +1,41 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Events/Expression.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/String.h"
namespace gd {
Expression::Expression() : node(nullptr) {};
Expression::Expression(gd::String plainString_)
: node(nullptr), plainString(plainString_) {};
Expression::Expression(const char* plainString_)
: node(nullptr), plainString(plainString_) {};
Expression::Expression(const Expression& copy)
: node(nullptr), plainString{copy.plainString} {};
Expression& Expression::operator=(const Expression& expression) {
plainString = expression.plainString;
node = nullptr;
return *this;
};
Expression::~Expression(){};
ExpressionNode* Expression::GetRootNode() const {
if (!node) {
gd::ExpressionParser2 parser = ExpressionParser2();
node = std::move(parser.ParseExpression(plainString));
}
return node.get();
}
} // namespace gd

View File

@@ -6,7 +6,15 @@
#ifndef GDCORE_EXPRESSION_H
#define GDCORE_EXPRESSION_H
#include "GDCore/String.h"
#include <memory>
namespace gd {
class ExpressionParser2;
class ObjectsContainer;
struct ExpressionNode;
} // namespace gd
namespace gd {
@@ -24,32 +32,49 @@ class GD_CORE_API Expression {
/**
* \brief Construct an empty expression
*/
Expression(){};
Expression();
/**
* \brief Construct an expression from a string
*/
Expression(gd::String plainString_) : plainString(plainString_){};
Expression(gd::String plainString_);
/**
* \brief Construct an expression from a const char *
*/
Expression(const char* plainString_) : plainString(plainString_){};
Expression(const char* plainString_);
/**
* \brief Copy construct an expression.
*/
Expression(const Expression& copy);
/**
* \brief Expression affectation overriding.
*/
Expression& operator=(const Expression& expression);
/**
* \brief Get the plain string representing the expression
*/
inline const gd::String& GetPlainString() const { return plainString; };
/**
* @brief Get the expression node.
* @return std::unique_ptr<gd::ExpressionNode>
*/
gd::ExpressionNode* GetRootNode() const;
/**
* \brief Mimics std::string::c_str
*/
inline const char* c_str() const { return plainString.c_str(); };
virtual ~Expression(){};
virtual ~Expression();
private:
gd::String plainString; ///< The expression string
mutable std::unique_ptr<gd::ExpressionNode> node;
};
} // namespace gd

View File

@@ -72,6 +72,22 @@ class GD_CORE_API Instruction {
*/
void SetInverted(bool inverted_) { inverted = inverted_; }
/**
* \brief Return true if the async instruction should be awaited.
* This is not relevant if the instruction is not optionally asynchronous.
*
* \return true if the instruction is to be awaited
*/
bool IsAwaited() const { return awaitAsync; }
/**
* \brief Set if the async instruction is to be awaited or not.
* This is not relevant if the instruction is not optionally asynchronous.
*
* \param inverted true if the instruction must be awaited
*/
void SetAwaited(bool awaited) { awaitAsync = awaited; }
/**
* \brief Return the number of parameters of the instruction.
*/
@@ -139,7 +155,9 @@ class GD_CORE_API Instruction {
* Useful to get reference to the original instruction in memory during code
* generation, to ensure stable unique identifiers.
*/
std::weak_ptr<Instruction> GetOriginalInstruction() { return originalInstruction; };
std::weak_ptr<Instruction> GetOriginalInstruction() {
return originalInstruction;
};
friend std::shared_ptr<Instruction> CloneRememberingOriginalElement(
std::shared_ptr<Instruction> instruction);
@@ -148,6 +166,9 @@ class GD_CORE_API Instruction {
gd::String type; ///< Instruction type
bool inverted; ///< True if the instruction if inverted. Only applicable for
///< instruction used as conditions by events
bool awaitAsync =
false; ///< Tells the code generator whether the optionally asynchronous
///< instruction should be generated as asynchronous (awaited) or not.
mutable std::vector<gd::Expression>
parameters; ///< Vector containing the parameters
gd::InstructionsList subInstructions; ///< Sub instructions, if applicable.

View File

@@ -26,132 +26,16 @@ namespace gd {
gd::String ExpressionParser2::NAMESPACE_SEPARATOR = "::";
ExpressionParser2::ExpressionParser2(
const gd::Platform& platform_,
const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_)
ExpressionParser2::ExpressionParser2()
: expression(""),
currentPosition(0),
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_) {}
namespace {
/**
* Return the minimum number of parameters, starting from a given parameter
* (by convention, 1 for object functions and 2 for behavior functions).
*/
size_t GetMinimumParametersNumber(
const std::vector<gd::ParameterMetadata>& parameters,
size_t initialParameterIndex) {
size_t nb = 0;
for (std::size_t i = initialParameterIndex; i < parameters.size(); ++i) {
if (!parameters[i].optional && !parameters[i].codeOnly) nb++;
}
return nb;
}
/**
* Return the maximum number of parameters, starting from a given parameter
* (by convention, 1 for object functions and 2 for behavior functions).
*/
size_t GetMaximumParametersNumber(
const std::vector<gd::ParameterMetadata>& parameters,
size_t initialParameterIndex) {
size_t nb = 0;
for (std::size_t i = initialParameterIndex; i < parameters.size(); ++i) {
if (!parameters[i].codeOnly) nb++;
}
return nb;
}
} // namespace
std::unique_ptr<ExpressionParserDiagnostic> ExpressionParser2::ValidateFunction(
const gd::String& type,
const gd::FunctionCallNode& function,
size_t functionStartPosition) {
if (gd::MetadataProvider::IsBadExpressionMetadata(
function.expressionMetadata)) {
return gd::make_unique<ExpressionParserError>(
"invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
functionStartPosition,
GetCurrentPosition());
}
// Validate the type of the function
const gd::String& returnType = function.expressionMetadata.GetReturnType();
if (returnType == "number") {
if (type == "string")
return RaiseTypeError(
_("You tried to use an expression that returns a number, but a "
"string is expected. Use `ToString` if you need to convert a "
"number to a string."),
functionStartPosition);
else if (type != "number" && type != "number|string")
return RaiseTypeError(_("You tried to use an expression that returns a "
"number, but another type is expected:") +
" " + type,
functionStartPosition);
} else if (returnType == "string") {
if (type == "number")
return RaiseTypeError(
_("You tried to use an expression that returns a string, but a "
"number is expected. Use `ToNumber` if you need to convert a "
"string to a number."),
functionStartPosition);
else if (type != "string" && type != "number|string")
return RaiseTypeError(_("You tried to use an expression that returns a "
"string, but another type is expected:") +
" " + type,
functionStartPosition);
} else {
if (type != returnType)
return RaiseTypeError(
_("You tried to use an expression with the wrong return type:") + " " +
returnType,
functionStartPosition);
}
// Validate parameters count
size_t minParametersCount = GetMinimumParametersNumber(
function.expressionMetadata.parameters,
WrittenParametersFirstIndex(function.objectName, function.behaviorName));
size_t maxParametersCount = GetMaximumParametersNumber(
function.expressionMetadata.parameters,
WrittenParametersFirstIndex(function.objectName, function.behaviorName));
if (function.parameters.size() < minParametersCount ||
function.parameters.size() > maxParametersCount) {
gd::String expectedCountMessage =
minParametersCount == maxParametersCount
? _("The number of parameters must be exactly ") +
gd::String::From(minParametersCount)
: _("The number of parameters must be: ") +
gd::String::From(minParametersCount) + "-" +
gd::String::From(maxParametersCount);
if (function.parameters.size() < minParametersCount) {
return gd::make_unique<ExpressionParserError>(
"too_few_parameters",
"You have not entered enough parameters for the expression. " +
expectedCountMessage,
functionStartPosition,
GetCurrentPosition());
}
}
return gd::make_unique<ExpressionParserDiagnostic>();
}
currentPosition(0) {}
std::unique_ptr<TextNode> ExpressionParser2::ReadText() {
size_t textStartPosition = GetCurrentPosition();
SkipAllWhitespaces();
if (!CheckIfChar(IsQuote)) {
auto text = gd::make_unique<TextNode>("");
// It can't happen.
text->diagnostic =
RaiseSyntaxError(_("A text must start with a double quote (\")."));
text->location =

View File

@@ -40,9 +40,7 @@ namespace gd {
*/
class GD_CORE_API ExpressionParser2 {
public:
ExpressionParser2(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_);
ExpressionParser2();
virtual ~ExpressionParser2(){};
/**
@@ -58,13 +56,11 @@ class GD_CORE_API ExpressionParser2 {
* \return The node representing the expression as a parsed tree.
*/
std::unique_ptr<ExpressionNode> ParseExpression(
const gd::String &type,
const gd::String &expression_,
const gd::String &objectName = "") {
const gd::String &expression_) {
expression = expression_;
currentPosition = 0;
return Start(type, objectName);
return Start();
}
/**
@@ -88,18 +84,16 @@ class GD_CORE_API ExpressionParser2 {
* Each method is a part of the grammar.
*/
///@{
std::unique_ptr<ExpressionNode> Start(const gd::String &type,
const gd::String &objectName = "") {
std::unique_ptr<ExpressionNode> Start() {
size_t expressionStartPosition = GetCurrentPosition();
auto expression = Expression(type, objectName);
const gd::String &inferredType = expression->type;
auto expression = Expression();
// Check for extra characters at the end of the expression
if (!IsEndReached()) {
auto op = gd::make_unique<OperatorNode>(inferredType, ' ');
auto op = gd::make_unique<OperatorNode>(' ');
op->leftHandSide = std::move(expression);
op->rightHandSide = ReadUntilEnd("unknown");
op->rightHandSide = ReadUntilEnd();
op->rightHandSide->parent = op.get();
op->rightHandSide->diagnostic = RaiseSyntaxError(
_("The expression has extra character at the end that should be "
@@ -113,61 +107,49 @@ class GD_CORE_API ExpressionParser2 {
return expression;
}
std::unique_ptr<ExpressionNode> Expression(
const gd::String &type, const gd::String &objectName = "") {
std::unique_ptr<ExpressionNode> Expression() {
SkipAllWhitespaces();
size_t expressionStartPosition = GetCurrentPosition();
std::unique_ptr<ExpressionNode> leftHandSide = Term(type, objectName);
const gd::String &inferredType = leftHandSide->type;
std::unique_ptr<ExpressionNode> leftHandSide = Term();
SkipAllWhitespaces();
if (IsEndReached()) return leftHandSide;
if (CheckIfChar(IsExpressionEndingChar)) return leftHandSide;
if (CheckIfChar(IsExpressionOperator)) {
auto op = gd::make_unique<OperatorNode>(inferredType, GetCurrentChar());
auto op = gd::make_unique<OperatorNode>(GetCurrentChar());
op->leftHandSide = std::move(leftHandSide);
op->diagnostic = ValidateOperator(inferredType, GetCurrentChar());
op->leftHandSide->parent = op.get();
op->diagnostic = ValidateOperator(GetCurrentChar());
SkipChar();
op->rightHandSide = Expression(inferredType, objectName);
op->rightHandSide = Expression();
op->rightHandSide->parent = op.get();
op->location = ExpressionParserLocation(expressionStartPosition,
GetCurrentPosition());
return std::move(op);
}
if (inferredType == "string") {
leftHandSide->diagnostic = RaiseSyntaxError(
"You must add the operator + between texts or expressions. For "
"example: \"Your name: \" + VariableString(PlayerName).");
} else if (inferredType == "number") {
leftHandSide->diagnostic = RaiseSyntaxError(
"No operator found. Did you forget to enter an operator (like +, -, "
"* or /) between numbers or expressions?");
} else {
leftHandSide->diagnostic = RaiseSyntaxError(
"More than one term was found. Verify that your expression is "
"properly written.");
}
leftHandSide->diagnostic = RaiseSyntaxError(
"More than one term was found. Verify that your expression is "
"properly written.");
auto op = gd::make_unique<OperatorNode>(inferredType, ' ');
auto op = gd::make_unique<OperatorNode>(' ');
op->leftHandSide = std::move(leftHandSide);
op->rightHandSide = Expression(inferredType, objectName);
op->leftHandSide->parent = op.get();
op->rightHandSide = Expression();
op->rightHandSide->parent = op.get();
op->location =
ExpressionParserLocation(expressionStartPosition, GetCurrentPosition());
return std::move(op);
}
std::unique_ptr<ExpressionNode> Term(const gd::String &type,
const gd::String &objectName) {
std::unique_ptr<ExpressionNode> Term() {
SkipAllWhitespaces();
size_t expressionStartPosition = GetCurrentPosition();
std::unique_ptr<ExpressionNode> factor = Factor(type, objectName);
const gd::String &inferredType = factor->type;
std::unique_ptr<ExpressionNode> factor = Factor();
SkipAllWhitespaces();
@@ -175,11 +157,13 @@ class GD_CORE_API ExpressionParser2 {
// to guarantee the proper operator precedence. (Expression could also
// be reworked to use a while loop).
while (CheckIfChar(IsTermOperator)) {
auto op = gd::make_unique<OperatorNode>(inferredType, GetCurrentChar());
auto op = gd::make_unique<OperatorNode>(GetCurrentChar());
op->leftHandSide = std::move(factor);
op->diagnostic = ValidateOperator(inferredType, GetCurrentChar());
op->leftHandSide->parent = op.get();
op->diagnostic = ValidateOperator(GetCurrentChar());
SkipChar();
op->rightHandSide = Factor(inferredType, objectName);
op->rightHandSide = Factor();
op->rightHandSide->parent = op.get();
op->location = ExpressionParserLocation(expressionStartPosition,
GetCurrentPosition());
SkipAllWhitespaces();
@@ -190,54 +174,35 @@ class GD_CORE_API ExpressionParser2 {
return factor;
};
std::unique_ptr<ExpressionNode> Factor(const gd::String &type,
const gd::String &objectName) {
std::unique_ptr<ExpressionNode> Factor() {
SkipAllWhitespaces();
size_t expressionStartPosition = GetCurrentPosition();
if (CheckIfChar(IsQuote)) {
std::unique_ptr<ExpressionNode> factor = ReadText();
if (type == "number")
factor->diagnostic =
RaiseTypeError(_("You entered a text, but a number was expected."),
expressionStartPosition);
else if (type != "string" && type != "number|string")
factor->diagnostic = RaiseTypeError(
_("You entered a text, but this type was expected:") + type,
expressionStartPosition);
return factor;
} else if (CheckIfChar(IsUnaryOperator)) {
auto unaryOperatorCharacter = GetCurrentChar();
SkipChar();
auto operatorOperand = Factor(type, objectName);
const gd::String &inferredType = operatorOperand->type;
auto operatorOperand = Factor();
auto unaryOperator = gd::make_unique<UnaryOperatorNode>(
inferredType, unaryOperatorCharacter);
unaryOperatorCharacter);
unaryOperator->diagnostic = ValidateUnaryOperator(
inferredType, unaryOperatorCharacter, expressionStartPosition);
unaryOperatorCharacter, expressionStartPosition);
unaryOperator->factor = std::move(operatorOperand);
unaryOperator->factor->parent = unaryOperator.get();
unaryOperator->location = ExpressionParserLocation(
expressionStartPosition, GetCurrentPosition());
return std::move(unaryOperator);
} else if (CheckIfChar(IsNumberFirstChar)) {
std::unique_ptr<ExpressionNode> factor = ReadNumber();
if (type == "string")
factor->diagnostic = RaiseTypeError(
_("You entered a number, but a text was expected (in quotes)."),
expressionStartPosition);
else if (type != "number" && type != "number|string")
factor->diagnostic = RaiseTypeError(
_("You entered a number, but this type was expected:") + type,
expressionStartPosition);
return factor;
} else if (CheckIfChar(IsOpeningParenthesis)) {
SkipChar();
std::unique_ptr<ExpressionNode> factor = SubExpression(type, objectName);
std::unique_ptr<ExpressionNode> factor = SubExpression();
if (!CheckIfChar(IsClosingParenthesis)) {
factor->diagnostic =
@@ -247,29 +212,20 @@ class GD_CORE_API ExpressionParser2 {
SkipIfChar(IsClosingParenthesis);
return factor;
} else if (IsIdentifierAllowedChar()) {
// This is a place where the grammar differs according to the
// type being expected.
if (gd::ParameterMetadata::IsExpression("variable", type)) {
return Variable(type, objectName);
} else {
return Identifier(type);
}
return Identifier();
}
std::unique_ptr<ExpressionNode> factor = ReadUntilWhitespace(type);
factor->diagnostic = RaiseEmptyError(type, expressionStartPosition);
std::unique_ptr<ExpressionNode> factor = ReadUntilWhitespace();
return factor;
}
std::unique_ptr<SubExpressionNode> SubExpression(
const gd::String &type, const gd::String &objectName) {
std::unique_ptr<SubExpressionNode> SubExpression() {
size_t expressionStartPosition = GetCurrentPosition();
auto expression = Expression(type, objectName);
const gd::String &inferredType = expression->type;
auto expression = Expression();
auto subExpression =
gd::make_unique<SubExpressionNode>(inferredType, std::move(expression));
gd::make_unique<SubExpressionNode>(std::move(expression));
subExpression->location =
ExpressionParserLocation(expressionStartPosition, GetCurrentPosition());
@@ -277,7 +233,7 @@ class GD_CORE_API ExpressionParser2 {
};
std::unique_ptr<IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode>
Identifier(const gd::String &type) {
Identifier() {
auto identifierAndLocation = ReadIdentifierName();
gd::String name = identifierAndLocation.name;
auto nameLocation = identifierAndLocation.location;
@@ -304,47 +260,28 @@ class GD_CORE_API ExpressionParser2 {
if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
return FreeFunction(type, name, nameLocation, openingParenthesisLocation);
return FreeFunction(name, nameLocation, openingParenthesisLocation);
} else if (CheckIfChar(IsDot)) {
ExpressionParserLocation dotLocation = SkipChar();
SkipAllWhitespaces();
return ObjectFunctionOrBehaviorFunction(
type, name, nameLocation, dotLocation);
} else {
auto identifier = gd::make_unique<IdentifierNode>(name, type);
if (type == "string") {
identifier->diagnostic =
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
nameLocation.GetStartPosition());
} else if (type == "number") {
identifier->diagnostic = RaiseTypeError(
_("You must enter a number."), nameLocation.GetStartPosition());
} else if (type == "number|string") {
identifier->diagnostic = RaiseTypeError(
_("You must enter a number or a text, wrapped inside double quotes "
"(example: \"Hello world\")."),
nameLocation.GetStartPosition());
} else if (!gd::ParameterMetadata::IsObject(type)) {
identifier->diagnostic = RaiseTypeError(
_("You've entered a name, but this type was expected:") + type,
nameLocation.GetStartPosition());
}
name, nameLocation, dotLocation);
} else if (CheckIfChar(IsOpeningSquareBracket)) {
return Variable(name, nameLocation);
}
else {
auto identifier = gd::make_unique<IdentifierNode>(name);
identifier->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
identifier->identifierNameLocation = identifier->location;
return std::move(identifier);
}
}
std::unique_ptr<VariableNode> Variable(const gd::String &type,
const gd::String &objectName) {
auto identifierAndLocation = ReadIdentifierName();
const gd::String &name = identifierAndLocation.name;
const auto &nameLocation = identifierAndLocation.location;
auto variable = gd::make_unique<VariableNode>(type, name, objectName);
std::unique_ptr<VariableNode> Variable(const gd::String &name, gd::ExpressionParserLocation nameLocation) {
auto variable = gd::make_unique<VariableNode>(name);
variable->child = VariableAccessorOrVariableBracketAccessor();
variable->child->parent = variable.get();
variable->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
@@ -359,8 +296,8 @@ class GD_CORE_API ExpressionParser2 {
SkipAllWhitespaces();
if (CheckIfChar(IsOpeningSquareBracket)) {
SkipChar();
auto child = gd::make_unique<VariableBracketAccessorNode>(
Expression("number|string"));
auto child = gd::make_unique<VariableBracketAccessorNode>(Expression());
child->expression->parent = child.get();
if (!CheckIfChar(IsClosingSquareBracket)) {
child->diagnostic =
@@ -369,6 +306,7 @@ class GD_CORE_API ExpressionParser2 {
}
SkipIfChar(IsClosingSquareBracket);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
child->location =
ExpressionParserLocation(childStartPosition, GetCurrentPosition());
@@ -381,6 +319,7 @@ class GD_CORE_API ExpressionParser2 {
auto child =
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
child->nameLocation = identifierAndLocation.location;
child->dotLocation = dotLocation;
child->location =
@@ -389,40 +328,21 @@ class GD_CORE_API ExpressionParser2 {
return std::move(child);
}
return std::move(
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>());
return std::move(gd::make_unique<VariableAccessorOrVariableBracketAccessorNode>());
}
std::unique_ptr<FunctionCallNode> FreeFunction(
const gd::String &type,
const gd::String &functionFullName,
const ExpressionParserLocation &identifierLocation,
const ExpressionParserLocation &openingParenthesisLocation) {
// TODO: error if trying to use function for type != "number" && != "string"
// + Test for it
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>(returnType,
std::move(parametersNode.parameters),
metadata,
functionFullName);
gd::make_unique<FunctionCallNode>(functionFullName);
auto parametersNode = Parameters(function.get());
function->parameters = std::move(parametersNode.parameters);
function->diagnostic = std::move(parametersNode.diagnostic);
if (!function->diagnostic) // TODO: reverse the order of diagnostic?
function->diagnostic = ValidateFunction(
type, *function, identifierLocation.GetStartPosition());
function->location = ExpressionParserLocation(
identifierLocation.GetStartPosition(), GetCurrentPosition());
@@ -434,16 +354,15 @@ class GD_CORE_API ExpressionParser2 {
return std::move(function);
}
std::unique_ptr<FunctionCallOrObjectFunctionNameOrEmptyNode>
std::unique_ptr<IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode>
ObjectFunctionOrBehaviorFunction(
const gd::String &type,
const gd::String &objectName,
const ExpressionParserLocation &objectNameLocation,
const ExpressionParserLocation &objectNameDotLocation) {
auto identifierAndLocation = ReadIdentifierName();
const gd::String &objectFunctionOrBehaviorName = identifierAndLocation.name;
const auto &objectFunctionOrBehaviorNameLocation =
identifierAndLocation.location;
const gd::String &parentIdentifier,
const ExpressionParserLocation &parentIdentifierLocation,
const ExpressionParserLocation &parentIdentifierDotLocation) {
auto childIdentifierAndLocation = ReadIdentifierName();
const gd::String &childIdentifierName = childIdentifierAndLocation.name;
const auto &childIdentifierNameLocation =
childIdentifierAndLocation.location;
SkipAllWhitespaces();
@@ -451,87 +370,68 @@ class GD_CORE_API ExpressionParser2 {
ExpressionParserLocation namespaceSeparatorLocation =
SkipNamespaceSeparator();
SkipAllWhitespaces();
return BehaviorFunction(type,
objectName,
objectFunctionOrBehaviorName,
objectNameLocation,
objectNameDotLocation,
objectFunctionOrBehaviorNameLocation,
return BehaviorFunction(parentIdentifier,
childIdentifierName,
parentIdentifierLocation,
parentIdentifierDotLocation,
childIdentifierNameLocation,
namespaceSeparatorLocation);
} else if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
gd::String objectType =
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName);
const gd::ExpressionMetadata &metadata =
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>(
returnType,
objectName,
std::move(parametersNode.parameters),
metadata,
objectFunctionOrBehaviorName);
parentIdentifier,
childIdentifierName);
auto parametersNode = Parameters(function.get(), parentIdentifier);
function->parameters = std::move(parametersNode.parameters),
function->diagnostic = std::move(parametersNode.diagnostic);
if (!function->diagnostic) // TODO: reverse the order of diagnostic?
function->diagnostic = ValidateFunction(
type, *function, objectNameLocation.GetStartPosition());
// If the function needs a capability on the object that may not be covered
// by all objects, check it now.
if (!metadata.GetRequiredBaseObjectCapability().empty()) {
const gd::ObjectMetadata &objectMetadata =
MetadataProvider::GetObjectMetadata(platform, objectType);
if (objectMetadata.IsUnsupportedBaseObjectCapability(
metadata.GetRequiredBaseObjectCapability())) {
function->diagnostic = RaiseTypeError(
_("This expression exists, but it can't be used on this object."),
objectNameLocation.GetStartPosition());
}
}
function->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
function->objectNameLocation = objectNameLocation;
function->objectNameDotLocation = objectNameDotLocation;
function->functionNameLocation = objectFunctionOrBehaviorNameLocation;
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
function->objectNameLocation = parentIdentifierLocation;
function->objectNameDotLocation = parentIdentifierDotLocation;
function->functionNameLocation = childIdentifierNameLocation;
function->openingParenthesisLocation = openingParenthesisLocation;
function->closingParenthesisLocation =
parametersNode.closingParenthesisLocation;
return std::move(function);
} else if (CheckIfChar(IsDot) || CheckIfChar(IsOpeningSquareBracket)) {
auto variable = gd::make_unique<VariableNode>(parentIdentifier);
auto child =
gd::make_unique<VariableAccessorNode>(childIdentifierName);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
child->nameLocation = childIdentifierNameLocation;
child->dotLocation = parentIdentifierDotLocation;
child->location = ExpressionParserLocation(
parentIdentifierDotLocation.GetStartPosition(), GetCurrentPosition());
variable->child = std::move(child);
variable->child->parent = variable.get();
variable->location = ExpressionParserLocation(
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
variable->nameLocation = parentIdentifierLocation;
return std::move(variable);
}
auto node = gd::make_unique<ObjectFunctionNameNode>(
type, objectName, objectFunctionOrBehaviorName);
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis (for an object expression), or double colon "
"(::) was expected (for a behavior expression)."));
auto node = gd::make_unique<IdentifierNode>(
parentIdentifier, childIdentifierName);
if (!CheckIfChar(IsParameterSeparator) && !CheckIfChar(IsClosingParenthesis) && !IsEndReached()) {
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis (for an object expression), a double colon "
"(:: for a behavior expression), a dot or an opening bracket (for "
"a child variable) where expected."));
}
node->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
node->objectNameLocation = objectNameLocation;
node->objectNameDotLocation = objectNameDotLocation;
node->objectFunctionOrBehaviorNameLocation =
objectFunctionOrBehaviorNameLocation;
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
node->identifierNameLocation = parentIdentifierLocation;
node->identifierNameDotLocation = parentIdentifierDotLocation;
node->childIdentifierNameLocation = childIdentifierNameLocation;
return std::move(node);
}
std::unique_ptr<FunctionCallOrObjectFunctionNameOrEmptyNode> BehaviorFunction(
const gd::String &type,
const gd::String &objectName,
const gd::String &behaviorName,
const ExpressionParserLocation &objectNameLocation,
@@ -547,35 +447,14 @@ class GD_CORE_API ExpressionParser2 {
if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();
gd::String behaviorType = GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, behaviorName);
const gd::ExpressionMetadata &metadata =
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>(
returnType,
objectName,
behaviorName,
std::move(parametersNode.parameters),
metadata,
functionName);
auto parametersNode =
Parameters(function.get(), objectName, behaviorName);
function->parameters = std::move(parametersNode.parameters);
function->diagnostic = std::move(parametersNode.diagnostic);
if (!function->diagnostic) // TODO: reverse the order of diagnostic?
function->diagnostic = ValidateFunction(
type, *function, objectNameLocation.GetStartPosition());
function->location = ExpressionParserLocation(
objectNameLocation.GetStartPosition(), GetCurrentPosition());
@@ -591,7 +470,7 @@ class GD_CORE_API ExpressionParser2 {
return std::move(function);
} else {
auto node = gd::make_unique<ObjectFunctionNameNode>(
type, objectName, behaviorName, functionName);
objectName, behaviorName, functionName);
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis was expected here to call a function."));
@@ -615,7 +494,7 @@ class GD_CORE_API ExpressionParser2 {
};
ParametersNode Parameters(
std::vector<gd::ParameterMetadata> parameterMetadata,
FunctionCallNode *functionCallNode,
const gd::String &objectName = "",
const gd::String &behaviorName = "") {
std::vector<std::unique_ptr<ExpressionNode>> parameters;
@@ -626,77 +505,25 @@ class GD_CORE_API ExpressionParser2 {
size_t parameterIndex =
WrittenParametersFirstIndex(objectName, behaviorName);
bool previousCharacterIsParameterSeparator = false;
while (!IsEndReached()) {
SkipAllWhitespaces();
if (CheckIfChar(IsClosingParenthesis)) {
if (CheckIfChar(IsClosingParenthesis) && !previousCharacterIsParameterSeparator) {
auto closingParenthesisLocation = SkipChar();
return ParametersNode{
std::move(parameters), nullptr, closingParenthesisLocation};
} else {
if (parameterIndex < parameterMetadata.size()) {
const gd::String &type = parameterMetadata[parameterIndex].GetType();
if (parameterMetadata[parameterIndex].IsCodeOnly()) {
// Do nothing, code only parameters are not written in expressions.
} else if (gd::ParameterMetadata::IsExpression("number", type)) {
parameters.push_back(Expression("number"));
} else if (gd::ParameterMetadata::IsExpression("string", type)) {
parameters.push_back(Expression("string"));
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
parameters.push_back(Expression(
type, lastObjectName.empty() ? objectName : lastObjectName));
} else if (gd::ParameterMetadata::IsObject(type)) {
size_t parameterStartPosition = GetCurrentPosition();
std::unique_ptr<ExpressionNode> objectExpression = Expression(type);
// Memorize the last object name. By convention, parameters that
// require an object (mainly, "objectvar" and "behavior") should be
// placed after the object in the list of parameters (if possible,
// just after). Search "lastObjectName" in the codebase for other
// place where this convention is enforced.
if (auto identifierNode =
dynamic_cast<IdentifierNode *>(objectExpression.get())) {
lastObjectName = identifierNode->identifierName;
} else {
objectExpression->diagnostic =
gd::make_unique<ExpressionParserError>(
"malformed_object_parameter",
_("An object name was expected but something else was "
"written. Enter just the name of the object for this "
"parameter."),
parameterStartPosition,
GetCurrentPosition());
}
parameters.push_back(std::move(objectExpression));
} else {
size_t parameterStartPosition = GetCurrentPosition();
parameters.push_back(Expression("unknown"));
parameters.back()->diagnostic =
gd::make_unique<ExpressionParserError>(
"unknown_parameter_type",
_("This function is improperly set up. Reach out to the "
"extension developer or a GDevelop maintainer to fix "
"this issue"),
parameterStartPosition,
GetCurrentPosition());
}
} else {
size_t parameterStartPosition = GetCurrentPosition();
parameters.push_back(Expression("unknown"));
parameters.back()
->diagnostic = gd::make_unique<ExpressionParserError>(
"extra_parameter",
_("This parameter was not expected by this expression. Remove it "
"or verify that you've entered the proper expression name."),
parameterStartPosition,
GetCurrentPosition());
}
SkipAllWhitespaces();
SkipIfChar(IsParameterSeparator);
parameterIndex++;
}
bool isEmptyParameter = CheckIfChar(IsParameterSeparator)
|| (CheckIfChar(IsClosingParenthesis) && previousCharacterIsParameterSeparator);
auto parameter = isEmptyParameter ? gd::make_unique<EmptyNode>() : Expression();
parameter->parent = functionCallNode;
parameters.push_back(std::move(parameter));
SkipAllWhitespaces();
previousCharacterIsParameterSeparator = CheckIfChar(IsParameterSeparator);
SkipIfChar(IsParameterSeparator);
parameterIndex++;
}
ExpressionParserLocation invalidClosingParenthesisLocation;
@@ -708,92 +535,32 @@ class GD_CORE_API ExpressionParser2 {
}
///@}
/** \name Validators
* Return a diagnostic if any error is found
*/
///@{
std::unique_ptr<ExpressionParserDiagnostic> ValidateFunction(
const gd::String &type,
const gd::FunctionCallNode &function,
size_t functionStartPosition);
std::unique_ptr<ExpressionParserDiagnostic> ValidateOperator(
const gd::String &type, gd::String::value_type operatorChar) {
if (type == "number") {
if (operatorChar == '+' || operatorChar == '-' || operatorChar == '/' ||
operatorChar == '*') {
return gd::make_unique<ExpressionParserDiagnostic>();
}
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("You've used an operator that is not supported. Operator should be "
"either +, -, / or *."),
GetCurrentPosition());
} else if (type == "string") {
if (operatorChar == '+') {
return gd::make_unique<ExpressionParserDiagnostic>();
}
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
GetCurrentPosition());
} else if (gd::ParameterMetadata::IsObject(type)) {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("Operators (+, -, /, *) can't be used with an object name. Remove "
"the operator."),
GetCurrentPosition());
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("Operators (+, -, /, *) can't be used in variable names. Remove "
"the operator from the variable name."),
GetCurrentPosition());
gd::String::value_type operatorChar) {
if (operatorChar == '+' || operatorChar == '-' || operatorChar == '/' ||
operatorChar == '*') {
return gd::make_unique<ExpressionParserDiagnostic>();
}
return gd::make_unique<ExpressionParserDiagnostic>();
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("You've used an operator that is not supported. Operator should be "
"either +, -, / or *."),
GetCurrentPosition());
}
std::unique_ptr<ExpressionParserDiagnostic> ValidateUnaryOperator(
const gd::String &type,
gd::String::value_type operatorChar,
size_t position) {
if (type == "number") {
if (operatorChar == '+' || operatorChar == '-') {
return gd::make_unique<ExpressionParserDiagnostic>();
}
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("You've used an \"unary\" operator that is not supported. Operator "
"should be "
"either + or -."),
position);
} else if (type == "string") {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts, and must be placed between two texts (or "
"expressions)."),
position);
} else if (gd::ParameterMetadata::IsObject(type)) {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("Operators (+, -) can't be used with an object name. Remove the "
"operator."),
position);
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("Operators (+, -) can't be used in variable names. Remove "
"the operator from the variable name."),
position);
if (operatorChar == '+' || operatorChar == '-') {
return gd::make_unique<ExpressionParserDiagnostic>();
}
return gd::make_unique<ExpressionParserDiagnostic>();
return gd::make_unique<ExpressionParserError>(
"invalid_operator",
_("You've used an \"unary\" operator that is not supported. Operator "
"should be "
"either + or -."),
position);
}
///@}
@@ -981,7 +748,7 @@ class GD_CORE_API ExpressionParser2 {
std::unique_ptr<NumberNode> ReadNumber();
std::unique_ptr<EmptyNode> ReadUntilWhitespace(gd::String type) {
std::unique_ptr<EmptyNode> ReadUntilWhitespace() {
size_t startPosition = GetCurrentPosition();
gd::String text;
while (currentPosition < expression.size() &&
@@ -990,13 +757,13 @@ class GD_CORE_API ExpressionParser2 {
currentPosition++;
}
auto node = gd::make_unique<EmptyNode>(type, text);
auto node = gd::make_unique<EmptyNode>(text);
node->location =
ExpressionParserLocation(startPosition, GetCurrentPosition());
return node;
}
std::unique_ptr<EmptyNode> ReadUntilEnd(gd::String type) {
std::unique_ptr<EmptyNode> ReadUntilEnd() {
size_t startPosition = GetCurrentPosition();
gd::String text;
while (currentPosition < expression.size()) {
@@ -1004,7 +771,7 @@ class GD_CORE_API ExpressionParser2 {
currentPosition++;
}
auto node = gd::make_unique<EmptyNode>(type, text);
auto node = gd::make_unique<EmptyNode>(text);
node->location =
ExpressionParserLocation(startPosition, GetCurrentPosition());
return node;
@@ -1037,34 +804,11 @@ class GD_CORE_API ExpressionParser2 {
return std::move(gd::make_unique<ExpressionParserError>(
"type_error", message, beginningPosition, GetCurrentPosition()));
}
std::unique_ptr<ExpressionParserError> RaiseEmptyError(
const gd::String &type, size_t beginningPosition) {
gd::String message;
if (type == "number") {
message = _("You must enter a number or a valid expression call.");
} else if (type == "string") {
message = _(
"You must enter a text (between quotes) or a valid expression call.");
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
message = _("You must enter a variable name.");
} else if (gd::ParameterMetadata::IsObject(type)) {
message = _("You must enter a valid object name.");
} else {
message = _("You must enter a valid expression.");
}
return std::move(RaiseTypeError(message, beginningPosition));
}
///@}
gd::String expression;
std::size_t currentPosition;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
static gd::String NAMESPACE_SEPARATOR;
};

View File

@@ -17,6 +17,7 @@ class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
struct FunctionCallNode;
} // namespace gd
namespace gd {
@@ -57,6 +58,10 @@ struct GD_CORE_API ExpressionParserDiagnostic {
* \brief An error that can be attached to a gd::ExpressionNode.
*/
struct GD_CORE_API ExpressionParserError : public ExpressionParserDiagnostic {
ExpressionParserError(const gd::String &type_,
const gd::String &message_,
const ExpressionParserLocation &location_)
: type(type_), message(message_), location(location_){};
ExpressionParserError(const gd::String &type_,
const gd::String &message_,
size_t position_)
@@ -86,7 +91,7 @@ struct GD_CORE_API ExpressionParserError : public ExpressionParserDiagnostic {
* an expression inherits from.
*/
struct GD_CORE_API ExpressionNode {
ExpressionNode(const gd::String &type_) : type(type_){};
ExpressionNode() : parent(nullptr) {};
virtual ~ExpressionNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker){};
@@ -97,17 +102,12 @@ struct GD_CORE_API ExpressionNode {
/// function can store the position of the
/// object name, the dot, the function
/// name, etc...
gd::String type; // Actual type of the node.
// "string", "number", type supported by
// gd::ParameterMetadata::IsObject, types supported by
// gd::ParameterMetadata::IsExpression or "unknown".
ExpressionNode *parent;
};
struct GD_CORE_API SubExpressionNode : public ExpressionNode {
SubExpressionNode(const gd::String &type_,
std::unique_ptr<ExpressionNode> expression_)
: ExpressionNode(type_), expression(std::move(expression_)){};
SubExpressionNode(std::unique_ptr<ExpressionNode> expression_)
: ExpressionNode(), expression(std::move(expression_)){};
virtual ~SubExpressionNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitSubExpressionNode(*this);
@@ -120,8 +120,8 @@ struct GD_CORE_API SubExpressionNode : public ExpressionNode {
* \brief An operator node. For example: "lhs + rhs".
*/
struct GD_CORE_API OperatorNode : public ExpressionNode {
OperatorNode(const gd::String &type_, gd::String::value_type op_)
: ExpressionNode(type_), op(op_){};
OperatorNode(gd::String::value_type op_)
: ExpressionNode(), op(op_){};
virtual ~OperatorNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitOperatorNode(*this);
@@ -136,8 +136,8 @@ struct GD_CORE_API OperatorNode : public ExpressionNode {
* \brief A unary operator node. For example: "-2".
*/
struct GD_CORE_API UnaryOperatorNode : public ExpressionNode {
UnaryOperatorNode(const gd::String &type_, gd::String::value_type op_)
: ExpressionNode(type_), op(op_){};
UnaryOperatorNode(gd::String::value_type op_)
: ExpressionNode(), op(op_){};
virtual ~UnaryOperatorNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitUnaryOperatorNode(*this);
@@ -153,7 +153,7 @@ struct GD_CORE_API UnaryOperatorNode : public ExpressionNode {
*/
struct GD_CORE_API NumberNode : public ExpressionNode {
NumberNode(const gd::String &number_)
: ExpressionNode("number"), number(number_){};
: ExpressionNode(), number(number_){};
virtual ~NumberNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitNumberNode(*this);
@@ -168,7 +168,7 @@ struct GD_CORE_API NumberNode : public ExpressionNode {
* Its `type` is always "string".
*/
struct GD_CORE_API TextNode : public ExpressionNode {
TextNode(const gd::String &text_) : ExpressionNode("string"), text(text_){};
TextNode(const gd::String &text_) : ExpressionNode(), text(text_){};
virtual ~TextNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitTextNode(*this);
@@ -177,32 +177,88 @@ struct GD_CORE_API TextNode : public ExpressionNode {
gd::String text;
};
struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
: public ExpressionNode {
IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode()
: ExpressionNode(){};
};
/**
* \brief An identifier node, usually representing an object or a variable
* with an optional function name or child variable name respectively.
*
* The name of a function to call on an object or the behavior,
* for example: "MyObject.Function" or "MyObject.Physics".
*
* A variable, potentially with accessor to its child,
* for example: MyVariable or MyVariable.MyChild
*/
struct GD_CORE_API IdentifierNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
IdentifierNode(
const gd::String &identifierName_)
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(),
identifierName(identifierName_),
childIdentifierName(""){};
IdentifierNode(
const gd::String &identifierName_,
const gd::String &childIdentifierName_)
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(),
identifierName(identifierName_),
childIdentifierName(childIdentifierName_){};
virtual ~IdentifierNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitIdentifierNode(*this);
};
gd::String identifierName; ///< The object or variable name.
gd::String childIdentifierName; ///< The object function or variable child name.
ExpressionParserLocation
identifierNameLocation; ///< Location of the object or variable name.
ExpressionParserLocation
identifierNameDotLocation; ///< Location of the "." after the object or variable name.
ExpressionParserLocation childIdentifierNameLocation; ///< Location of object
/// function, behavior or
/// child variable name.
};
struct GD_CORE_API FunctionCallOrObjectFunctionNameOrEmptyNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
FunctionCallOrObjectFunctionNameOrEmptyNode()
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(){};
virtual ~FunctionCallOrObjectFunctionNameOrEmptyNode(){};
void Visit(ExpressionParser2NodeWorker &worker) override{};
};
struct GD_CORE_API VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
VariableAccessorOrVariableBracketAccessorNode() : ExpressionNode(""){};
VariableAccessorOrVariableBracketAccessorNode() : ExpressionNode(){};
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode> child;
};
/**
* \brief A variable, potentially with accessor to its children.
*
* Example: MyVariable or MyVariable.MyChildren
* \brief A variable with bracket accessor or at least 2 "dot" accessors.
*
* Example: MyVariable[MyChildren] or MyVariable.MyChildren.MyGranChildren.
*
* Other cases like "MyVariable" or "MyVariable.MyChildren" are IdentifierNode
* to allow handling ambiguities.
*
* \see gd::IdentifierNode
* \see gd::VariableAccessorNode
* \see gd::VariableBracketAccessorNode
*/
struct GD_CORE_API VariableNode : public ExpressionNode {
VariableNode(const gd::String &type_,
const gd::String &name_,
const gd::String &objectName_)
: ExpressionNode(type_), name(name_), objectName(objectName_){};
struct GD_CORE_API VariableNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
VariableNode(const gd::String &name_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(), name(name_){};
virtual ~VariableNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitVariableNode(*this);
};
gd::String name;
gd::String objectName;
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode>
child; // Can be nullptr if no accessor
@@ -216,7 +272,8 @@ struct GD_CORE_API VariableNode : public ExpressionNode {
*/
struct GD_CORE_API VariableAccessorNode
: public VariableAccessorOrVariableBracketAccessorNode {
VariableAccessorNode(const gd::String &name_) : name(name_){};
VariableAccessorNode(const gd::String &name_)
: VariableAccessorOrVariableBracketAccessorNode(), name(name_){};
virtual ~VariableAccessorNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitVariableAccessorNode(*this);
@@ -234,7 +291,7 @@ struct GD_CORE_API VariableAccessorNode
struct GD_CORE_API VariableBracketAccessorNode
: public VariableAccessorOrVariableBracketAccessorNode {
VariableBracketAccessorNode(std::unique_ptr<ExpressionNode> expression_)
: expression(std::move(expression_)){};
: VariableAccessorOrVariableBracketAccessorNode(), expression(std::move(expression_)){};
virtual ~VariableBracketAccessorNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitVariableBracketAccessorNode(*this);
@@ -243,55 +300,26 @@ struct GD_CORE_API VariableBracketAccessorNode
std::unique_ptr<ExpressionNode> expression;
};
struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
: public ExpressionNode {
IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(
const gd::String &type)
: ExpressionNode(type){};
};
/**
* \brief An identifier node, usually representing an object or a function name.
*/
struct GD_CORE_API IdentifierNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
IdentifierNode(const gd::String &identifierName_, const gd::String &type_)
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type_),
identifierName(identifierName_){};
virtual ~IdentifierNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitIdentifierNode(*this);
};
gd::String identifierName;
};
struct GD_CORE_API FunctionCallOrObjectFunctionNameOrEmptyNode
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
FunctionCallOrObjectFunctionNameOrEmptyNode(const gd::String &type)
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type){};
virtual ~FunctionCallOrObjectFunctionNameOrEmptyNode(){};
void Visit(ExpressionParser2NodeWorker &worker) override{};
};
/**
* \brief The name of a function to call on an object or the behavior
* For example: "MyObject.Function" or "MyObject.Physics" or
* "MyObject.Physics::LinearVelocity".
* For example: "MyObject.Physics::LinearVelocity".
*
* Other cases like "MyObject.Function" or "MyObject.Physics" are IdentifierNode
* to allow handling ambiguities.
*
* \see gd::IdentifierNode
*/
struct GD_CORE_API ObjectFunctionNameNode
: public FunctionCallOrObjectFunctionNameOrEmptyNode {
ObjectFunctionNameNode(const gd::String &type_,
const gd::String &objectName_,
ObjectFunctionNameNode(const gd::String &objectName_,
const gd::String &objectFunctionOrBehaviorName_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_),
: FunctionCallOrObjectFunctionNameOrEmptyNode(),
objectName(objectName_),
objectFunctionOrBehaviorName(objectFunctionOrBehaviorName_) {}
ObjectFunctionNameNode(const gd::String &type_,
const gd::String &objectName_,
ObjectFunctionNameNode(const gd::String &objectName_,
const gd::String &behaviorName_,
const gd::String &behaviorFunctionName_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_),
: FunctionCallOrObjectFunctionNameOrEmptyNode(),
objectName(objectName_),
objectFunctionOrBehaviorName(behaviorName_),
behaviorFunctionName(behaviorFunctionName_) {}
@@ -334,39 +362,24 @@ struct GD_CORE_API ObjectFunctionNameNode
*/
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_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_),
parameters(std::move(parameters_)),
expressionMetadata(expressionMetadata_),
FunctionCallNode(const gd::String &functionName_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(),
functionName(functionName_){};
/** \brief Construct an object function call node. */
FunctionCallNode(const gd::String &type_,
const gd::String &objectName_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
FunctionCallNode(const gd::String &objectName_,
const gd::String &functionName_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_),
: FunctionCallOrObjectFunctionNameOrEmptyNode(),
objectName(objectName_),
parameters(std::move(parameters_)),
expressionMetadata(expressionMetadata_),
functionName(functionName_){};
/** \brief Construct a behavior function call node. */
FunctionCallNode(const gd::String &type_,
const gd::String &objectName_,
FunctionCallNode(const gd::String &objectName_,
const gd::String &behaviorName_,
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
const ExpressionMetadata &expressionMetadata_,
const gd::String &functionName_)
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_),
: FunctionCallOrObjectFunctionNameOrEmptyNode(),
objectName(objectName_),
behaviorName(behaviorName_),
parameters(std::move(parameters_)),
expressionMetadata(expressionMetadata_),
functionName(functionName_){};
virtual ~FunctionCallNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
@@ -376,7 +389,6 @@ struct GD_CORE_API FunctionCallNode : public FunctionCallOrObjectFunctionNameOrE
gd::String objectName;
gd::String behaviorName;
std::vector<std::unique_ptr<ExpressionNode>> parameters;
const ExpressionMetadata &expressionMetadata;
gd::String functionName;
ExpressionParserLocation
@@ -401,8 +413,8 @@ struct GD_CORE_API FunctionCallNode : public FunctionCallOrObjectFunctionNameOrE
* encountered and any other node could not make sense.
*/
struct GD_CORE_API EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
EmptyNode(const gd::String &type_, const gd::String &text_ = "")
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_), text(text_){};
EmptyNode(const gd::String &text_ = "")
: FunctionCallOrObjectFunctionNameOrEmptyNode(), text(text_){};
virtual ~EmptyNode(){};
virtual void Visit(ExpressionParser2NodeWorker &worker) {
worker.OnVisitEmptyNode(*this);

View File

@@ -91,6 +91,9 @@ class GD_CORE_API ExpressionParser2NodePrinter
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
output += node.identifierName;
if (!node.childIdentifierName.empty()) {
output += "." + node.childIdentifierName;
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty()) {

View File

@@ -4,6 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Events/Serialization.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
@@ -232,7 +233,8 @@ void EventsListSerialization::SerializeEventsTo(const EventsList& list,
const gd::BaseEvent& event = list.GetEvent(j);
SerializerElement& eventElem = events.AddChild("event");
if (event.IsDisabled()) eventElem.SetAttribute("disabled", event.IsDisabled());
if (event.IsDisabled())
eventElem.SetAttribute("disabled", event.IsDisabled());
if (event.IsFolded()) eventElem.SetAttribute("folded", event.IsFolded());
eventElem.AddChild("type").SetValue(event.GetType());
@@ -267,6 +269,9 @@ void gd::EventsListSerialization::UnserializeInstructionsFrom(
instrElement.GetChild("type", 0, "Type")
.GetBoolAttribute("inverted", false, "Contraire"));
instruction.SetAwaited(
instrElement.GetChild("type", 0, "Type").GetBoolAttribute("await"));
// Read parameters
vector<gd::Expression> parameters;
@@ -340,9 +345,12 @@ void gd::EventsListSerialization::SerializeInstructionsTo(
instructions.ConsiderAsArrayOf("instruction");
for (std::size_t k = 0; k < list.size(); k++) {
SerializerElement& instruction = instructions.AddChild("instruction");
instruction.AddChild("type")
.SetAttribute("value", list[k].GetType())
.SetAttribute("inverted", list[k].IsInverted());
instruction.AddChild("type").SetAttribute("value", list[k].GetType());
if (list[k].IsInverted())
instruction.GetChild("type").SetAttribute("inverted", true);
if (list[k].IsAwaited())
instruction.GetChild("type").SetAttribute("await", true);
// Parameters
SerializerElement& parameters = instruction.AddChild("parameters");
@@ -352,9 +360,10 @@ void gd::EventsListSerialization::SerializeInstructionsTo(
.SetValue(list[k].GetParameter(l).GetPlainString());
// Sub instructions
SerializerElement& subInstructions =
instruction.AddChild("subInstructions");
SerializeInstructionsTo(list[k].GetSubInstructions(), subInstructions);
if (!list[k].GetSubInstructions().empty()) {
SerializeInstructionsTo(list[k].GetSubInstructions(),
instruction.AddChild("subInstructions"));
}
}
}

View File

@@ -14,14 +14,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAudioExtension(
extension
.SetExtensionInformation(
"BuiltinAudio",
_("Sounds and musics"),
_("Sounds and music"),
_("GDevelop provides several conditions and actions to play audio "
"files. They can be either long musics or short sound effects."),
"files. They can be either long music or short sound effects."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/audio")
.SetCategory("Audio");
extension.AddInstructionOrExpressionGroupMetadata(_("Sounds and musics"))
extension.AddInstructionOrExpressionGroupMetadata(_("Sounds and music"))
.SetIcon("res/actions/music24.png");
extension

View File

@@ -219,7 +219,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddAction("SetAngle",
_("Angle"),
_("Change the angle of rotation of an object."),
_("Change the angle of rotation of an object (in degrees)."),
_("the angle"),
_("Angle"),
"res/actions/direction24.png",
@@ -289,7 +289,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Speed on X axis (in pixels per second)"))
.AddParameter("expression", _("Speed on Y axis (in pixels per second)"))
.AddParameter("forceMultiplier", _("Force multiplier"));
.AddParameter("forceMultiplier", _("Force multiplier"), "", true)
.SetDefaultValue("0");
obj.AddAction("AddForceAL",
_("Add a force (angle)"),
@@ -305,7 +306,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Angle"))
.AddParameter("expression", _("Speed (in pixels per second)"))
.AddParameter("forceMultiplier", _("Force multiplier"))
.AddParameter("forceMultiplier", _("Force multiplier"), "", true)
.SetDefaultValue("0")
.MarkAsAdvanced();
obj.AddAction(
@@ -323,7 +325,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("expression", _("X position"))
.AddParameter("expression", _("Y position"))
.AddParameter("expression", _("Speed (in pixels per second)"))
.AddParameter("forceMultiplier", _("Force multiplier"))
.AddParameter("forceMultiplier", _("Force multiplier"), "", true)
.SetDefaultValue("0")
.MarkAsAdvanced();
obj.AddAction(
@@ -756,7 +759,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("object", _("Object"))
.AddParameter("objectPtr", _("Target Object"))
.AddParameter("expression", _("Speed (in pixels per second)"))
.AddParameter("forceMultiplier", _("Force multiplier"))
.AddParameter("forceMultiplier", _("Force multiplier"), "", true)
.SetDefaultValue("0")
.MarkAsAdvanced();
obj.AddAction(
@@ -776,7 +780,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddParameter("objectPtr", _("Rotate around this object"))
.AddParameter("expression", _("Speed (in degrees per second)"))
.AddParameter("expression", _("Distance (in pixels)"))
.AddParameter("forceMultiplier", _("Force multiplier"))
.AddParameter("forceMultiplier", _("Force multiplier"), "", true)
.SetDefaultValue("0")
.MarkAsAdvanced();
obj.AddAction("MettreAutour",
@@ -993,7 +998,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpression("ForceAngle",
_("Angle of the sum of forces"),
_("Angle of the sum of forces"),
_("Angle of the sum of forces (in degrees)"),
_("Movement using forces"),
"res/actions/force.png")
.AddParameter("object", _("Object"));
@@ -1126,8 +1131,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpression("AngleToObject",
_("Angle between two objects"),
_("Compute the angle between two objects. If you need the "
"angle to an arbitrary position, use AngleToPosition."),
_("Compute the angle between two objects (in degrees). "
"If you need the angle to an arbitrary position, "
"use AngleToPosition."),
_("Angle"),
"res/actions/position.png")
.AddParameter("object", _("Object"))
@@ -1160,8 +1166,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpression("AngleToPosition",
_("Angle between an object and a position"),
_("Compute the angle between the object center and a "
"\"target\" position. If you need the angle between two "
"objects, use AngleToObject."),
"\"target\" position (in degrees). If you need the angle "
"between two objects, use AngleToObject."),
_("Angle"),
"res/actions/position.png")
.AddParameter("object", _("Object"))

View File

@@ -29,7 +29,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddExpressionAndConditionAndAction(
"number",
"CameraX",
"CameraCenterX",
_("Camera center X position"),
_("the X position of the center of a camera"),
_("the X position of camera _PARAM4_ (layer: _PARAM3_)"),
@@ -43,15 +43,25 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.SetDefaultValue("0")
.MarkAsAdvanced();
// Compatibility with GD <= 5.0.135
extension.AddDuplicatedCondition("CameraX", "CameraCenterX")
.SetHidden(); // Deprecated
extension.AddDuplicatedExpression("CameraX", "CameraCenterX")
.SetHidden(); // Deprecated
extension.AddDuplicatedAction("SetCameraX", "SetCameraCenterX")
.SetHidden(); // Deprecated
extension.AddDuplicatedAction("CameraX", "SetCameraX")
.SetHidden(); // Deprecated
extension.AddDuplicatedExpression("VueX", "CameraX")
.SetHidden(); // Deprecated
// end of compatibility code
extension
.AddExpressionAndConditionAndAction(
"number",
"CameraY",
"CameraCenterY",
_("Camera center Y position"),
_("the Y position of the center of a camera"),
_("the Y position of camera _PARAM4_ (layer: _PARAM3_)"),
@@ -65,10 +75,20 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.SetDefaultValue("0")
.MarkAsAdvanced();
// Compatibility with GD <= 5.0.135
extension.AddDuplicatedCondition("CameraY", "CameraCenterY")
.SetHidden(); // Deprecated
extension.AddDuplicatedExpression("CameraY", "CameraCenterY")
.SetHidden(); // Deprecated
extension.AddDuplicatedAction("SetCameraY", "SetCameraCenterY")
.SetHidden(); // Deprecated
extension.AddDuplicatedAction("CameraY", "SetCameraY")
.SetHidden(); // Deprecated
extension.AddDuplicatedExpression("VueY", "CameraY")
.SetHidden(); // Deprecated
// end of compatibility code
extension
.AddExpressionAndCondition(
@@ -80,9 +100,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"",
"res/conditions/camera24.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"))
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("expression", _("Camera number"))
.AddParameter("expression", _("Camera number"), "", true)
.UseStandardParameters("number")
.MarkAsAdvanced();
@@ -96,9 +116,77 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"",
"res/conditions/camera24.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"))
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("expression", _("Camera number"))
.AddParameter("expression", _("Camera number"), "", true)
.UseStandardParameters("number")
.MarkAsAdvanced();
extension
.AddExpressionAndCondition(
"number",
"CameraBorderLeft",
_("Camera left border position"),
_("the position of the left border of a camera"),
_("the position of the left border of camera _PARAM2_ of layer "
"_PARAM1_"),
"",
"res/conditions/camera24.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("expression", _("Camera number"), "", true)
.UseStandardParameters("number")
.MarkAsAdvanced();
extension
.AddExpressionAndCondition(
"number",
"CameraBorderRight",
_("Camera right border position"),
_("the position of the right border of a camera"),
_("the position of the right border of camera _PARAM2_ of layer "
"_PARAM1_"),
"",
"res/conditions/camera24.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("expression", _("Camera number"), "", true)
.UseStandardParameters("number")
.MarkAsAdvanced();
extension
.AddExpressionAndCondition(
"number",
"CameraBorderTop",
_("Camera top border position"),
_("the position of the top border of a camera"),
_("the position of the top border of camera _PARAM2_ of layer "
"_PARAM1_"),
"",
"res/conditions/camera24.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("expression", _("Camera number"), "", true)
.UseStandardParameters("number")
.MarkAsAdvanced();
extension
.AddExpressionAndCondition(
"number",
"CameraBorderBottom",
_("Camera bottom border position"),
_("the position of the bottom border of a camera"),
_("the position of the bottom border of camera _PARAM2_ of layer "
"_PARAM1_"),
"",
"res/conditions/camera24.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("layer", _("Layer (base layer if empty)"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("expression", _("Camera number"), "", true)
.UseStandardParameters("number")
.MarkAsAdvanced();
@@ -107,7 +195,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
"number",
"CameraAngle",
_("Angle of a camera of a layer"),
_("the angle of rotation of a camera"),
_("the angle of rotation of a camera (in degrees)"),
_("the angle of camera (layer: _PARAM3_, camera: _PARAM4_)"),
"",
"res/conditions/camera24.png")

View File

@@ -96,13 +96,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
_("Difference between two angles"),
"",
"res/mathfunction.png")
.AddParameter("expression", _("First angle"))
.AddParameter("expression", _("Second angle"));
.AddParameter("expression", _("First angle, in degrees"))
.AddParameter("expression", _("Second angle, in degrees"));
extension
.AddExpression("AngleBetweenPositions",
_("Angle between two positions"),
_("Compute the angle between two positions."),
_("Compute the angle between two positions (in degrees)."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("First point X position"))
@@ -159,7 +159,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("acos",
_("Arccosine"),
_("Arccosine"),
_("Arccosine, return an angle (in radian). "
"`ToDeg` allows to convert it to degrees."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
@@ -175,7 +176,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("asin",
_("Arcsine"),
_("Arcsine"),
_("Arcsine, return an angle (in radian). "
"`ToDeg` allows to convert it to degrees."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
@@ -191,7 +193,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("atan",
_("Arctangent"),
_("Arctangent"),
_("Arctangent, return an angle (in radian). "
"`ToDeg` allows to convert it to degrees."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
@@ -258,7 +261,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("cos",
_("Cosine"),
_("Cosine of a number"),
_("Cosine of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
@@ -400,7 +404,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("sin",
_("Sine"),
_("Sine of a number"),
_("Sine of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));
@@ -424,7 +429,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
extension
.AddExpression("tan",
_("Tangent"),
_("Tangent of a number"),
_("Tangent of an angle (in radian). "
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
"",
"res/mathfunction.png")
.AddParameter("expression", _("Expression"));

View File

@@ -21,9 +21,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/timers-and-time");
extension.AddInstructionOrExpressionGroupMetadata(
_("Timers and time")
)
extension.AddInstructionOrExpressionGroupMetadata(_("Timers and time"))
.SetIcon("res/conditions/timer24.png");
// Deprecated and replaced by CompareTimer
@@ -158,8 +156,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
"res/timer.svg",
"res/timer.svg")
.AddParameter("expression", "Time to wait in seconds")
.SetHelpPath("/all-features/timers-and-time/wait-action")
.SetAsync();
.SetHelpPath("/all-features/timers-and-time/wait-action");
extension
.AddExpression("TimeDelta",

View File

@@ -22,7 +22,6 @@ InstructionMetadata::InstructionMetadata()
canHaveSubInstructions(false),
hidden(true),
usageComplexity(5),
isAsync(false),
isPrivate(false),
isObjectInstruction(false),
isBehaviorInstruction(false) {}
@@ -46,7 +45,6 @@ InstructionMetadata::InstructionMetadata(const gd::String& extensionNamespace_,
extensionNamespace(extensionNamespace_),
hidden(false),
usageComplexity(5),
isAsync(false),
isPrivate(false),
isObjectInstruction(false),
isBehaviorInstruction(false) {}

View File

@@ -6,10 +6,10 @@
#ifndef INSTRUCTIONMETADATA_H
#define INSTRUCTIONMETADATA_H
#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <algorithm>
#include "GDCore/Events/Instruction.h"
#include "GDCore/String.h"
@@ -104,16 +104,16 @@ class GD_CORE_API InstructionMetadata {
* background, executing the instructions following it before the frame after
* it resolved.
*/
bool IsAsync() const { return isAsync; }
bool IsAsync() const {
return !codeExtraInformation.asyncFunctionCallName.empty();
}
/**
* Set that the instruction is asynchronous - it will be running in the
* background, executing the instructions following it before the frame after
* it resolved.
* Check if the instruction asynchronicity is optional. If it is, it can either
* be used synchronously or asynchronously, with one function for each.
*/
InstructionMetadata &SetAsync() {
isAsync = true;
return *this;
bool IsOptionallyAsync() const {
return IsAsync() && !codeExtraInformation.functionCallName.empty();
}
/**
@@ -319,14 +319,26 @@ class GD_CORE_API InstructionMetadata {
virtual ~ExtraInformation(){};
/**
* Set the function name which will be used when generating the code.
* \param functionName the name of the function to call
* Set the name of the function which will be called in the generated code.
* \param functionName the name of the function to call.
*/
ExtraInformation &SetFunctionName(const gd::String &functionName_) {
functionCallName = functionName_;
return *this;
}
/**
* Set the name of the function, doing asynchronous work, which will be called in
* the generated code. This function should return an asynchronous task
* (i.e: `gdjs.AsyncTask` in the JavaScript runtime).
*
* \param functionName the name of the function doing asynchronous work to call.
*/
ExtraInformation &SetAsyncFunctionName(const gd::String &functionName_) {
asyncFunctionCallName = functionName_;
return *this;
}
/**
* Declare if the instruction being declared is somewhat manipulating in a
* standard way.
@@ -354,7 +366,7 @@ class GD_CORE_API InstructionMetadata {
* .AddParameter("object", _("Object"), "Text", false)
* .AddParameter("operator", _("Modification operator"), "string")
* .AddParameter("string", _("String"))
* .SetFunctionName("SetString").SetManipulatedType("string").SetGetter("GetString").SetIncludeFile("MyExtension/TextObject.h");
* .SetFunctionName("SetString").SetManipulatedType("string").SetGetter("GetString");
*
* DECLARE_END_OBJECT_ACTION()
* \endcode
@@ -422,6 +434,7 @@ class GD_CORE_API InstructionMetadata {
bool HasCustomCodeGenerator() const { return hasCustomCodeGenerator; }
gd::String functionCallName;
gd::String asyncFunctionCallName;
gd::String type;
AccessType accessType;
gd::String optionalAssociatedInstruction;
@@ -454,15 +467,26 @@ class GD_CORE_API InstructionMetadata {
}
/**
* \brief Set the function that should be called when generating the source
* code from events.
* \param functionName the name of the function to call
* Set the name of the function which will be called in the generated code.
* \param functionName the name of the function to call.
* \note Shortcut for `codeExtraInformation.SetFunctionName`.
*/
ExtraInformation &SetFunctionName(const gd::String &functionName) {
return codeExtraInformation.SetFunctionName(functionName);
}
/**
* Set the name of the function, doing asynchronous work, which will be called in
* the generated code. This function should return an asynchronous task
* (i.e: `gdjs.AsyncTask` in the JavaScript runtime).
*
* \param functionName the name of the function doing asynchronous work to call.
* \note Shortcut for `codeExtraInformation.SetAsyncFunctionName`.
*/
ExtraInformation &SetAsyncFunctionName(const gd::String &functionName) {
return codeExtraInformation.SetAsyncFunctionName(functionName);
}
std::vector<ParameterMetadata> parameters;
private:
@@ -479,7 +503,6 @@ class GD_CORE_API InstructionMetadata {
int usageComplexity; ///< Evaluate the instruction from 0 (simple&easy to
///< use) to 10 (complex to understand)
bool isPrivate;
bool isAsync;
bool isObjectInstruction;
bool isBehaviorInstruction;
gd::String requiredBaseObjectCapability;

View File

@@ -13,7 +13,9 @@
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/String.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
using namespace std;
@@ -30,12 +32,9 @@ ExtensionAndMetadata<BehaviorMetadata>
MetadataProvider::GetExtensionAndBehaviorMetadata(const gd::Platform& platform,
gd::String behaviorType) {
for (auto& extension : platform.GetAllPlatformExtensions()) {
auto behaviorTypes = extension->GetBehaviorsTypes();
for (std::size_t j = 0; j < behaviorTypes.size(); ++j) {
if (behaviorTypes[j] == behaviorType)
return ExtensionAndMetadata<BehaviorMetadata>(
*extension, extension->GetBehaviorMetadata(behaviorType));
}
if (extension->HasBehavior(behaviorType))
return ExtensionAndMetadata<BehaviorMetadata>(
*extension, extension->GetBehaviorMetadata(behaviorType));
}
return ExtensionAndMetadata<BehaviorMetadata>(badExtension, badBehaviorMetadata);
@@ -202,8 +201,7 @@ MetadataProvider::GetExtensionAndBehaviorExpressionMetadata(
const gd::Platform& platform, gd::String autoType, gd::String exprType) {
auto& extensions = platform.GetAllPlatformExtensions();
for (auto& extension : extensions) {
const auto& autos = extension->GetBehaviorsTypes();
if (find(autos.begin(), autos.end(), autoType) != autos.end()) {
if (extension->HasBehavior(autoType)) {
const auto& allAutoExpressions =
extension->GetAllExpressionsForBehavior(autoType);
if (allAutoExpressions.find(exprType) != allAutoExpressions.end())
@@ -292,8 +290,7 @@ MetadataProvider::GetExtensionAndBehaviorStrExpressionMetadata(
const gd::Platform& platform, gd::String autoType, gd::String exprType) {
auto& extensions = platform.GetAllPlatformExtensions();
for (auto& extension : extensions) {
const auto& autos = extension->GetBehaviorsTypes();
if (find(autos.begin(), autos.end(), autoType) != autos.end()) {
if (extension->HasBehavior(autoType)) {
const auto& allBehaviorStrExpressions =
extension->GetAllStrExpressionsForBehavior(autoType);
if (allBehaviorStrExpressions.find(exprType) !=
@@ -350,28 +347,30 @@ const gd::ExpressionMetadata& MetadataProvider::GetAnyExpressionMetadata(
const gd::Platform& platform, gd::String exprType) {
const auto& numberExpressionMetadata =
GetExpressionMetadata(platform, exprType);
if (&numberExpressionMetadata != &badExpressionMetadata) {
return numberExpressionMetadata;
}
const auto& stringExpressionMetadata =
GetStrExpressionMetadata(platform, exprType);
return &numberExpressionMetadata != &badExpressionMetadata
? numberExpressionMetadata
: &stringExpressionMetadata != &badExpressionMetadata
? stringExpressionMetadata
: badExpressionMetadata;
if (&stringExpressionMetadata != &badExpressionMetadata) {
return stringExpressionMetadata;
}
return badExpressionMetadata;
}
const gd::ExpressionMetadata& MetadataProvider::GetObjectAnyExpressionMetadata(
const gd::Platform& platform, gd::String objectType, gd::String exprType) {
const auto& numberExpressionMetadata =
GetObjectExpressionMetadata(platform, objectType, exprType);
if (&numberExpressionMetadata != &badExpressionMetadata) {
return numberExpressionMetadata;
}
const auto& stringExpressionMetadata =
GetObjectStrExpressionMetadata(platform, objectType, exprType);
return &numberExpressionMetadata != &badExpressionMetadata
? numberExpressionMetadata
: &stringExpressionMetadata != &badExpressionMetadata
? stringExpressionMetadata
: badExpressionMetadata;
if (&stringExpressionMetadata != &badExpressionMetadata) {
return stringExpressionMetadata;
}
return badExpressionMetadata;
}
const gd::ExpressionMetadata&
@@ -380,14 +379,98 @@ MetadataProvider::GetBehaviorAnyExpressionMetadata(const gd::Platform& platform,
gd::String exprType) {
const auto& numberExpressionMetadata =
GetBehaviorExpressionMetadata(platform, autoType, exprType);
if (&numberExpressionMetadata != &badExpressionMetadata) {
return numberExpressionMetadata;
}
const auto& stringExpressionMetadata =
GetBehaviorStrExpressionMetadata(platform, autoType, exprType);
if (&stringExpressionMetadata != &badExpressionMetadata) {
return stringExpressionMetadata;
}
return badExpressionMetadata;
}
return &numberExpressionMetadata != &badExpressionMetadata
? numberExpressionMetadata
: &stringExpressionMetadata != &badExpressionMetadata
? stringExpressionMetadata
: badExpressionMetadata;
const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
FunctionCallNode& node) {
if (!node.behaviorName.empty()) {
gd::String behaviorType =
GetTypeOfBehavior(globalObjectsContainer, objectsContainer, node.behaviorName);
return MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, node.functionName);
}
else if (!node.objectName.empty()) {
gd::String objectType =
GetTypeOfObject(globalObjectsContainer, objectsContainer, node.objectName);
return MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, node.functionName);
}
return MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
}
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
FunctionCallNode& functionCall,
ExpressionNode& parameter) {
int parameterIndex = -1;
for (int i = 0; i < functionCall.parameters.size(); i++) {
if (functionCall.parameters.at(i).get() == &parameter) {
parameterIndex = i;
break;
}
}
if (parameterIndex < 0) {
return nullptr;
}
return MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
functionCall,
parameterIndex);
}
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
FunctionCallNode& functionCall,
int parameterIndex) {
// Search the parameter metadata index skipping invisible ones.
size_t visibleParameterIndex = 0;
size_t metadataParameterIndex =
ExpressionParser2::WrittenParametersFirstIndex(
functionCall.objectName, functionCall.behaviorName);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, functionCall);
if (IsBadExpressionMetadata(metadata)) {
return nullptr;
}
// TODO use a badMetadata instead of a nullptr?
const gd::ParameterMetadata* parameterMetadata = nullptr;
while (metadataParameterIndex <
metadata.parameters.size()) {
if (!metadata.parameters[metadataParameterIndex]
.IsCodeOnly()) {
if (visibleParameterIndex == parameterIndex) {
parameterMetadata = &metadata.parameters[metadataParameterIndex];
}
visibleParameterIndex++;
}
metadataParameterIndex++;
}
const int visibleParameterCount = visibleParameterIndex;
// It can be null if there are too many parameters in the expression, this text node is
// not actually linked to a parameter expected by the function call.
return parameterMetadata;
}
MetadataProvider::~MetadataProvider() {}

View File

@@ -15,6 +15,8 @@ class ExpressionMetadata;
class ExpressionMetadata;
class Platform;
class PlatformExtension;
struct FunctionCallNode;
struct ExpressionNode;
} // namespace gd
namespace gd {
@@ -234,6 +236,26 @@ class GD_CORE_API MetadataProvider {
static const gd::ExpressionMetadata& GetObjectAnyExpressionMetadata(
const gd::Platform& platform, gd::String objectType, gd::String exprType);
static const gd::ExpressionMetadata& GetFunctionCallMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
FunctionCallNode& node);
static const gd::ParameterMetadata* GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
FunctionCallNode& functionCall,
ExpressionNode& parameter);
static const gd::ParameterMetadata* GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
FunctionCallNode& functionCall,
int parameterIndex);
/**
* Get information about an expression from its type.
* Works for behavior expressions.

View File

@@ -35,4 +35,18 @@ void ParameterMetadata::UnserializeFrom(const SerializerElement& element) {
name = element.GetStringAttribute("name");
}
// TODO factorize in a file with an enum and helpers?
const gd::String ParameterMetadata::numberType = "number";
const gd::String ParameterMetadata::stringType = "string";
const gd::String &ParameterMetadata::GetExpressionValueType(const gd::String &parameterType) {
if (parameterType == "number" || gd::ParameterMetadata::IsExpression("number", parameterType)) {
return ParameterMetadata::numberType;
}
if (parameterType == "string" || gd::ParameterMetadata::IsExpression("string", parameterType)) {
return ParameterMetadata::stringType;
}
return parameterType;
}
} // namespace gd

View File

@@ -207,6 +207,15 @@ class GD_CORE_API ParameterMetadata {
return false;
}
/**
* \brief Return the expression type from the parameter type.
* Declinations of "number" and "string" types (like "forceMultiplier" or
* "sceneName") are replaced by "number" and "string".
*/
static const gd::String &GetExpressionValueType(const gd::String &parameterType);
static const gd::String numberType;
static const gd::String stringType;
/** \name Serialization
*/
///@{

View File

@@ -14,7 +14,7 @@
namespace gd {
void ParameterMetadataTools::ParametersToObjectsContainer(
gd::Project& project,
const gd::Project& project,
const std::vector<gd::ParameterMetadata>& parameters,
gd::ObjectsContainer& outputObjectsContainer) {
outputObjectsContainer.GetObjects().clear();
@@ -59,13 +59,13 @@ void ParameterMetadataTools::IterateOverParameters(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
const gd::String& lastObjectName)> fn) {
IterateOverParametersWithIndex(
parameters,
parametersMetadata,
[&fn](const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
fn(parameterMetadata, parameterValue, lastObjectName);
@@ -76,17 +76,17 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn) {
gd::String lastObjectName = "";
for (std::size_t pNb = 0; pNb < parametersMetadata.size(); ++pNb) {
const gd::ParameterMetadata& parameterMetadata = parametersMetadata[pNb];
const gd::String& parameterValue =
const gd::Expression& parameterValue =
pNb < parameters.size() ? parameters[pNb].GetPlainString() : "";
const gd::String& parameterValueOrDefault =
parameterValue.empty() && parameterMetadata.optional
? parameterMetadata.GetDefaultValue()
const gd::Expression& parameterValueOrDefault =
parameterValue.GetPlainString().empty() && parameterMetadata.optional
? Expression(parameterMetadata.GetDefaultValue())
: parameterValue;
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName);
@@ -97,7 +97,7 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
// Search "lastObjectName" in the codebase for other place where this
// convention is enforced.
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType()))
lastObjectName = parameterValueOrDefault;
lastObjectName = parameterValueOrDefault.GetPlainString();
}
}

View File

@@ -19,7 +19,7 @@ namespace gd {
class GD_CORE_API ParameterMetadataTools {
public:
static void ParametersToObjectsContainer(
gd::Project& project,
const gd::Project& project,
const std::vector<gd::ParameterMetadata>& parameters,
gd::ObjectsContainer& outputObjectsContainer);
@@ -32,7 +32,7 @@ class GD_CORE_API ParameterMetadataTools {
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
const gd::String& lastObjectName)> fn);
/**
@@ -44,7 +44,7 @@ class GD_CORE_API ParameterMetadataTools {
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn);

View File

@@ -346,6 +346,11 @@ gd::BehaviorMetadata& PlatformExtension::GetBehaviorMetadata(
return badBehaviorMetadata;
}
bool PlatformExtension::HasBehavior(
const gd::String& behaviorType) const {
return behaviorsInfo.find(behaviorType) != behaviorsInfo.end();
}
gd::EffectMetadata& PlatformExtension::GetEffectMetadata(
const gd::String& effectName) {
if (effectsMetadata.find(effectName) != effectsMetadata.end())
@@ -375,7 +380,7 @@ gd::InstructionMetadata& PlatformExtension::AddDuplicatedAction(
auto copiedAction = actionsInfos.find(copiedNameWithNamespace);
if (copiedAction == actionsInfos.end()) {
gd::LogWarning("Could not find an action with name " +
gd::LogError("Could not find an action with name " +
copiedNameWithNamespace + " to copy.");
} else {
actionsInfos[newNameWithNamespace] = copiedAction->second;
@@ -395,7 +400,7 @@ gd::InstructionMetadata& PlatformExtension::AddDuplicatedCondition(
auto copiedCondition = conditionsInfos.find(copiedNameWithNamespace);
if (copiedCondition == conditionsInfos.end()) {
gd::LogWarning("Could not find a condition with name " +
gd::LogError("Could not find a condition with name " +
copiedNameWithNamespace + " to copy.");
} else {
conditionsInfos[newNameWithNamespace] = copiedCondition->second;
@@ -412,7 +417,7 @@ gd::ExpressionMetadata& PlatformExtension::AddDuplicatedExpression(
auto copiedExpression = expressionsInfos.find(copiedNameWithNamespace);
if (copiedExpression == expressionsInfos.end()) {
gd::LogWarning("Could not find an expression with name " +
gd::LogError("Could not find an expression with name " +
copiedNameWithNamespace + " to copy.");
} else {
expressionsInfos[newNameWithNamespace] = copiedExpression->second;
@@ -429,7 +434,7 @@ gd::ExpressionMetadata& PlatformExtension::AddDuplicatedStrExpression(
auto copiedExpression = strExpressionsInfos.find(copiedNameWithNamespace);
if (copiedExpression == strExpressionsInfos.end()) {
gd::LogWarning("Could not find a string expression with name " +
gd::LogError("Could not find a string expression with name " +
copiedNameWithNamespace + " to copy.");
} else {
strExpressionsInfos[newNameWithNamespace] = copiedExpression->second;

View File

@@ -467,6 +467,12 @@ class GD_CORE_API PlatformExtension {
*/
BehaviorMetadata& GetBehaviorMetadata(const gd::String& behaviorType);
/**
* \brief Return true if the extension contains a behavior associated to \a
* behaviorType
*/
bool HasBehavior(const gd::String& behaviorType) const;
/**
* \brief Return the metadata for the effect with the given name.
*/

View File

@@ -11,7 +11,6 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
@@ -118,27 +117,20 @@ bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.type;
if (gd::ParameterMetadata::IsBehavior(type)) {
if (lastObjectName == objectName) {
if (parameterValue == oldBehaviorName) {
if (parameterValue.GetPlainString() == oldBehaviorName) {
instruction.SetParameter(parameterIndex,
gd::Expression(newBehaviorName));
}
}
} else {
gd::ExpressionParser2 parser(
platform, GetGlobalObjectsContainer(), GetObjectsContainer());
auto node =
gd::ParameterMetadata::IsExpression("number", type)
? parser.ParseExpression("number", parameterValue)
: (gd::ParameterMetadata::IsExpression("string", type)
? parser.ParseExpression("string", parameterValue)
: std::unique_ptr<gd::ExpressionNode>());
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionBehaviorRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),

View File

@@ -9,7 +9,6 @@
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
@@ -17,6 +16,7 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
@@ -31,7 +31,17 @@ namespace gd {
class GD_CORE_API ExpressionObjectsAnalyzer
: public ExpressionParser2NodeWorker {
public:
ExpressionObjectsAnalyzer(EventsContext& context_) : context(context_){};
ExpressionObjectsAnalyzer(
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String &rootType_,
EventsContext& context_) :
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
rootType(rootType_),
context(context_){};
virtual ~ExpressionObjectsAnalyzer(){};
protected:
@@ -59,7 +69,8 @@ class GD_CORE_API ExpressionObjectsAnalyzer
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (gd::ParameterMetadata::IsObject(node.type)) {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
context.AddObjectName(node.identifierName);
}
}
@@ -87,6 +98,11 @@ class GD_CORE_API ExpressionObjectsAnalyzer
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String rootType;
EventsContext& context;
};
@@ -102,7 +118,7 @@ bool EventsContextAnalyzer::DoVisitInstruction(gd::Instruction& instruction,
instruction.GetParameters(),
instrInfo.parameters,
[this](const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterValue,
const gd::String& lastObjectName) {
AnalyzeParameter(platform,
project,
@@ -129,16 +145,14 @@ void EventsContextAnalyzer::AnalyzeParameter(
if (ParameterMetadata::IsObject(type)) {
context.AddObjectName(value);
} else if (ParameterMetadata::IsExpression("number", type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("number", value);
auto node = parameter.GetRootNode();
ExpressionObjectsAnalyzer analyzer(context);
ExpressionObjectsAnalyzer analyzer(platform, project, layout, "number", context);
node->Visit(analyzer);
} else if (ParameterMetadata::IsExpression("string", type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("string", value);
auto node = parameter.GetRootNode();
ExpressionObjectsAnalyzer analyzer(context);
ExpressionObjectsAnalyzer analyzer(platform, project, layout, "string", context);
node->Visit(analyzer);
} else if (ParameterMetadata::IsBehavior(type)) {
context.AddBehaviorName(lastObjectName, value);

View File

@@ -0,0 +1,41 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsLeaderboardsLister.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
namespace gd {
bool EventsLeaderboardsLister::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const gd::InstructionMetadata& instrInfo =
isCondition ? MetadataProvider::GetConditionMetadata(
project.GetCurrentPlatform(), instruction.GetType())
: MetadataProvider::GetActionMetadata(
project.GetCurrentPlatform(), instruction.GetType());
for (int i = 0; i < instruction.GetParametersCount() &&
i < instrInfo.GetParametersCount();
++i)
if (instrInfo.GetParameter(i).type == "leaderboardId") {
leaderboardIds.insert(instruction.GetParameter(i).GetPlainString());
}
return false;
}
EventsLeaderboardsLister::~EventsLeaderboardsLister() {}
} // namespace gd

View File

@@ -0,0 +1,48 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EventsLeaderboardsLister_H
#define EventsLeaderboardsLister_H
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class Project;
class EventsList;
}
namespace gd {
/**
* \brief List the leaderboard ids in the instructions.
*
* \ingroup IDE
*/
class GD_CORE_API EventsLeaderboardsLister : public ArbitraryEventsWorker {
public:
EventsLeaderboardsLister(gd::Project& project_) : project(project_){};
virtual ~EventsLeaderboardsLister();
/**
* Return the values of all leaderboardIds found in the events.
*/
const std::set<gd::String>& GetLeaderboardIds() { return leaderboardIds; }
private:
virtual bool DoVisitInstruction(gd::Instruction& instruction,
bool isCondition);
std::set<gd::String> leaderboardIds;
gd::Project& project;
};
} // namespace gd
#endif // EventsLeaderboardsLister_H

View File

@@ -0,0 +1,49 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsLeaderboardsRenamer.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
namespace gd {
bool EventsLeaderboardsRenamer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const gd::InstructionMetadata& instrInfo =
isCondition ? MetadataProvider::GetConditionMetadata(
project.GetCurrentPlatform(), instruction.GetType())
: MetadataProvider::GetActionMetadata(
project.GetCurrentPlatform(), instruction.GetType());
for (int i = 0; i < instruction.GetParametersCount() &&
i < instrInfo.GetParametersCount();
++i) {
const gd::ParameterMetadata parameter = instrInfo.GetParameter(i);
if (parameter.type == "leaderboardId") {
const gd::String leaderboardId =
instruction.GetParameter(i).GetPlainString();
if (leaderboardIdMap.find(leaderboardId) != leaderboardIdMap.end()) {
instruction.SetParameter(i, leaderboardIdMap[leaderboardId]);
}
}
}
return false;
}
EventsLeaderboardsRenamer::~EventsLeaderboardsRenamer() {}
} // namespace gd

View File

@@ -0,0 +1,46 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EventsLeaderboardsRenamer_H
#define EventsLeaderboardsRenamer_H
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class Project;
class EventsList;
}
namespace gd {
/**
* \brief Replace the leaderboard ids in the instructions.
*
* \ingroup IDE
*/
class GD_CORE_API EventsLeaderboardsRenamer : public ArbitraryEventsWorker {
public:
EventsLeaderboardsRenamer(
gd::Project& project_,
const std::map<gd::String, gd::String>& leaderboardIdMap_)
: project(project_), leaderboardIdMap(leaderboardIdMap_){};
virtual ~EventsLeaderboardsRenamer();
private:
virtual bool DoVisitInstruction(gd::Instruction& instruction,
bool isCondition);
std::map<gd::String, gd::String> leaderboardIdMap;
gd::Project& project;
};
} // namespace gd
#endif // EventsLeaderboardsRenamer_H

View File

@@ -11,15 +11,15 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
using namespace std;
@@ -34,18 +34,30 @@ const gd::String EventsRefactorer::searchIgnoredCharacters = ";:,#()";
*/
class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
public:
ExpressionObjectRenamer(const gd::String& objectName_,
ExpressionObjectRenamer(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String &rootType_,
const gd::String& objectName_,
const gd::String& objectNewName_)
: hasDoneRenaming(false),
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
rootType(rootType_),
hasDoneRenaming(false),
objectName(objectName_),
objectNewName(objectNewName_){};
virtual ~ExpressionObjectRenamer(){};
static bool Rename(gd::ExpressionNode& node,
static bool Rename(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::String &rootType,
gd::ExpressionNode& node,
const gd::String& objectName,
const gd::String& objectNewName) {
if (ExpressionValidator::HasNoErrors(node)) {
ExpressionObjectRenamer renamer(objectName, objectNewName);
if (gd::ExpressionValidator::HasNoErrors(platform, globalObjectsContainer, objectsContainer, rootType, node)) {
ExpressionObjectRenamer renamer(platform, globalObjectsContainer, objectsContainer, rootType, objectName, objectNewName);
node.Visit(renamer);
return renamer.HasDoneRenaming();
@@ -81,7 +93,8 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (gd::ParameterMetadata::IsObject(node.type) &&
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
hasDoneRenaming = true;
node.identifierName = objectNewName;
@@ -108,6 +121,11 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
bool hasDoneRenaming;
const gd::String& objectName;
const gd::String& objectNewName;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String rootType;
};
/**
@@ -118,14 +136,27 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
*/
class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
public:
ExpressionObjectFinder(const gd::String& objectName_)
: hasObject(false), objectName(objectName_){};
ExpressionObjectFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String &rootType_,
const gd::String& objectName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
rootType(rootType_),
hasObject(false),
objectName(objectName_){};
virtual ~ExpressionObjectFinder(){};
static bool CheckIfHasObject(gd::ExpressionNode& node,
static bool CheckIfHasObject(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::String &rootType,
gd::ExpressionNode& node,
const gd::String& objectName) {
if (ExpressionValidator::HasNoErrors(node)) {
ExpressionObjectFinder finder(objectName);
if (gd::ExpressionValidator::HasNoErrors(platform, globalObjectsContainer, objectsContainer, rootType, node)) {
ExpressionObjectFinder finder(platform, globalObjectsContainer, objectsContainer, rootType, objectName);
node.Visit(finder);
return finder.HasFoundObject();
@@ -161,7 +192,8 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (gd::ParameterMetadata::IsObject(node.type) &&
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
hasObject = true;
}
@@ -184,6 +216,11 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
private:
bool hasObject;
const gd::String& objectName;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String rootType;
};
bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
@@ -205,11 +242,9 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
// Replace object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"number", actions[aId].GetParameter(pNb).GetPlainString());
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
actions[aId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -217,11 +252,9 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
// Replace object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"string", actions[aId].GetParameter(pNb).GetPlainString());
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
actions[aId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -263,11 +296,9 @@ bool EventsRefactorer::RenameObjectInConditions(
// Replace object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"number", conditions[cId].GetParameter(pNb).GetPlainString());
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
conditions[cId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -275,11 +306,9 @@ bool EventsRefactorer::RenameObjectInConditions(
// Replace object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"string", conditions[cId].GetParameter(pNb).GetPlainString());
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
conditions[cId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -316,20 +345,18 @@ bool EventsRefactorer::RenameObjectInEventParameters(
// Replace object's name in expressions
else if (ParameterMetadata::IsExpression("number",
parameterMetadata.GetType())) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("number", expression.GetPlainString());
auto node = expression.GetRootNode();
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
// Replace object's name in text expressions
else if (ParameterMetadata::IsExpression("string",
parameterMetadata.GetType())) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression("string", expression.GetPlainString());
auto node = expression.GetRootNode();
if (ExpressionObjectRenamer::Rename(*node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
@@ -405,11 +432,9 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
// Find object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"number", actions[aId].GetParameter(pNb).GetPlainString());
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, project, layout, "number", *node, name)) {
deleteMe = true;
break;
}
@@ -417,11 +442,9 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
// Find object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"string", actions[aId].GetParameter(pNb).GetPlainString());
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, project, layout, "string", *node, name)) {
deleteMe = true;
break;
}
@@ -469,11 +492,9 @@ bool EventsRefactorer::RemoveObjectInConditions(
// Find object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"number", conditions[cId].GetParameter(pNb).GetPlainString());
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, project, layout, "number", *node, name)) {
deleteMe = true;
break;
}
@@ -481,11 +502,9 @@ bool EventsRefactorer::RemoveObjectInConditions(
// Find object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"string", conditions[cId].GetParameter(pNb).GetPlainString());
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(*node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, project, layout, "string", *node, name)) {
deleteMe = true;
break;
}
@@ -535,16 +554,19 @@ void EventsRefactorer::RemoveObjectInEvents(const gd::Platform& platform,
}
}
void EventsRefactorer::ReplaceStringInEvents(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::EventsList& events,
gd::String toReplace,
gd::String newString,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventStrings) {
std::vector<EventsSearchResult> EventsRefactorer::ReplaceStringInEvents(
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::EventsList& events,
gd::String toReplace,
gd::String newString,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventStrings) {
vector<EventsSearchResult> modifiedEvents;
for (std::size_t i = 0; i < events.size(); ++i) {
bool eventModified = false;
if (inConditions) {
vector<gd::InstructionsList*> conditionsVectors =
events[i].GetAllConditionsVectors();
@@ -556,6 +578,13 @@ void EventsRefactorer::ReplaceStringInEvents(gd::ObjectsContainer& project,
toReplace,
newString,
matchCase);
if (conditionsModified && !eventModified) {
modifiedEvents.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
i));
eventModified = true;
}
}
}
@@ -569,25 +598,45 @@ void EventsRefactorer::ReplaceStringInEvents(gd::ObjectsContainer& project,
toReplace,
newString,
matchCase);
if (actionsModified && !eventModified) {
modifiedEvents.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
i));
eventModified = true;
}
}
}
if (inEventStrings) {
bool eventStringModified = ReplaceStringInEventSearchableStrings(
project, layout, events[i], toReplace, newString, matchCase);
if (eventStringModified && !eventModified) {
modifiedEvents.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
i));
eventModified = true;
}
}
if (events[i].CanHaveSubEvents())
ReplaceStringInEvents(project,
layout,
events[i].GetSubEvents(),
toReplace,
newString,
matchCase,
inConditions,
inActions,
inEventStrings);
if (events[i].CanHaveSubEvents()) {
std::vector<EventsSearchResult> modifiedSubEvent =
ReplaceStringInEvents(project,
layout,
events[i].GetSubEvents(),
toReplace,
newString,
matchCase,
inConditions,
inActions,
inEventStrings);
std::copy(modifiedSubEvent.begin(),
modifiedSubEvent.end(),
std::back_inserter(modifiedEvents));
}
}
return modifiedEvents;
}
gd::String ReplaceAllOccurencesCaseUnsensitive(gd::String context,
@@ -718,14 +767,16 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
bool inEventSentences) {
vector<EventsSearchResult> results;
const gd::String& ignored_characters = EventsRefactorer::searchIgnoredCharacters;
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.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(), ' ');
@@ -737,8 +788,11 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
events[i].GetAllConditionsVectors();
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
if (!eventAddedInResults &&
SearchStringInConditions(
platform, *conditionsVectors[j], search, matchCase, inEventSentences)) {
SearchStringInConditions(platform,
*conditionsVectors[j],
search,
matchCase,
inEventSentences)) {
results.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
@@ -751,9 +805,11 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
vector<gd::InstructionsList*> actionsVectors =
events[i].GetAllActionsVectors();
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
if (!eventAddedInResults &&
SearchStringInActions(
platform, *actionsVectors[j], search, matchCase, inEventSentences)) {
if (!eventAddedInResults && SearchStringInActions(platform,
*actionsVectors[j],
search,
matchCase,
inEventSentences)) {
results.push_back(EventsSearchResult(
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
&events,
@@ -790,12 +846,11 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
return results;
}
bool EventsRefactorer::SearchStringInActions(
const gd::Platform& platform,
gd::InstructionsList& actions,
gd::String search,
bool matchCase,
bool inSentences) {
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) {
@@ -826,27 +881,30 @@ bool EventsRefactorer::SearchStringInActions(
return false;
}
bool EventsRefactorer::SearchStringInFormattedText(
const gd::Platform& platform,
gd::Instruction& instruction,
gd::String search,
bool matchCase,
bool isCondition) {
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);
? 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;
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.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(), ' ');

View File

@@ -7,6 +7,7 @@
#define GDCORE_EVENTSREFACTORER_H
#include <memory>
#include <vector>
#include "GDCore/Events/Instruction.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/String.h"
@@ -18,7 +19,7 @@ class ExternalEvents;
class BaseEvent;
class Instruction;
typedef std::shared_ptr<gd::BaseEvent> BaseEventSPtr;
}
} // namespace gd
namespace gd {
@@ -43,10 +44,11 @@ class GD_CORE_API EventsSearchResult {
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.
* \brief Get the events list containing the event pointed by the
* EventsSearchResult. \warning Only call this when IsEventsListValid returns
* true.
*/
const gd::EventsList & GetEventsList() const { return *eventsList; }
const gd::EventsList& GetEventsList() const { return *eventsList; }
std::size_t GetPositionInList() const { return positionInList; }
@@ -56,7 +58,7 @@ class GD_CORE_API EventsSearchResult {
* \brief Get the event pointed by the EventsSearchResult.
* \warning Only call this when IsEventValid returns true.
*/
const gd::BaseEvent & GetEvent() const { return *event.lock(); }
const gd::BaseEvent& GetEvent() const { return *event.lock(); }
};
/**
@@ -98,27 +100,31 @@ class GD_CORE_API EventsRefactorer {
* \return A vector containing EventsSearchResult objects filled with events
* containing the string
*/
static std::vector<EventsSearchResult> SearchInEvents(const gd::Platform& platform,
gd::EventsList& events,
gd::String search,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventStrings,
bool inEventSentences);
static std::vector<EventsSearchResult> SearchInEvents(
const gd::Platform& platform,
gd::EventsList& events,
gd::String search,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventStrings,
bool inEventSentences);
/**
* Replace all occurrences of a gd::String in events
*
* \return A vector of all modified events.
*/
static void ReplaceStringInEvents(gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::EventsList& events,
gd::String toReplace,
gd::String newString,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventString);
static std::vector<EventsSearchResult> ReplaceStringInEvents(
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::EventsList& events,
gd::String toReplace,
gd::String newString,
bool matchCase,
bool inConditions,
bool inActions,
bool inEventString);
virtual ~EventsRefactorer(){};
@@ -148,20 +154,21 @@ class GD_CORE_API EventsRefactorer {
gd::InstructionsList& instructions,
gd::String oldName,
gd::String newName);
/**
/**
* Replace all occurrences of an object name by another name in an expression
* with the specified metadata
* ( include : objects or objects in math/text expressions ).
*
* \return true if something was modified.
*/
static bool RenameObjectInEventParameters(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::Expression& expression,
gd::ParameterMetadata parameterMetadata,
gd::String oldName,
gd::String newName);
static bool RenameObjectInEventParameters(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::Expression& expression,
gd::ParameterMetadata parameterMetadata,
gd::String oldName,
gd::String newName);
/**
* Remove all conditions of the list using an object

View File

@@ -7,7 +7,6 @@
#include "EventsVariablesFinder.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
@@ -32,10 +31,16 @@ namespace gd {
class GD_CORE_API ExpressionParameterSearcher
: public ExpressionParser2NodeWorker {
public:
ExpressionParameterSearcher(std::set<gd::String>& results_,
ExpressionParameterSearcher(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
std::set<gd::String>& results_,
const gd::String& parameterType_,
const gd::String& objectName_ = "")
: results(results_),
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
results(results_),
parameterType(parameterType_),
objectName(objectName_){};
virtual ~ExpressionParameterSearcher(){};
@@ -68,10 +73,22 @@ class GD_CORE_API ExpressionParameterSearcher
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
bool considerFunction = objectName.empty() || node.objectName == objectName;
const gd::ExpressionMetadata &metadata = node.objectName.empty() ?
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName) :
MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
node.functionName);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
return;
}
for (size_t i = 0; i < node.parameters.size() &&
i < node.expressionMetadata.parameters.size();
i < metadata.parameters.size();
++i) {
auto& parameterMetadata = node.expressionMetadata.parameters[i];
auto& parameterMetadata = metadata.parameters[i];
if (considerFunction && parameterMetadata.GetType() == parameterType) {
// Store the value of the parameter
results.insert(
@@ -84,6 +101,10 @@ class GD_CORE_API ExpressionParameterSearcher
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
std::set<gd::String>& results; ///< Reference to the std::set where argument
///< values must be stored.
gd::String parameterType; ///< The type of the parameters to be searched for.
@@ -165,24 +186,18 @@ std::set<gd::String> EventsVariablesFinder::FindArgumentsInInstructions(
}
// Search in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"number", instructions[aId].GetParameter(pNb).GetPlainString());
ExpressionParameterSearcher searcher(
results, parameterType, objectName);
node->Visit(searcher);
}
// Search in gd::String expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].type) ||
ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].type)) {
gd::ExpressionParser2 parser(platform, project, layout);
auto node = parser.ParseExpression(
"number", instructions[aId].GetParameter(pNb).GetPlainString());
auto node = instructions[aId].GetParameter(pNb).GetRootNode();
ExpressionParameterSearcher searcher(
results, parameterType, objectName);
platform,
project,
layout,
results,
parameterType,
objectName);
node->Visit(searcher);
}
// Remember the value of the last "object" parameter.

View File

@@ -15,6 +15,8 @@
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
namespace gd {
class Expression;
@@ -290,7 +292,11 @@ class GD_CORE_API ExpressionCompletionFinder
* and returns completions for it.
*/
static std::vector<ExpressionCompletionDescription>
GetCompletionDescriptionsFor(gd::ExpressionNode& node,
GetCompletionDescriptionsFor(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::String &rootType,
gd::ExpressionNode& node,
size_t searchedPosition) {
gd::ExpressionNodeLocationFinder finder(searchedPosition);
node.Visit(finder);
@@ -303,6 +309,7 @@ class GD_CORE_API ExpressionCompletionFinder
gd::ExpressionNode* maybeParentNodeAtLocation = finder.GetParentNode();
gd::ExpressionCompletionFinder autocompletionProvider(
platform, globalObjectsContainer, objectsContainer, rootType,
searchedPosition, maybeParentNodeAtLocation);
nodeAtLocation->Visit(autocompletionProvider);
return autocompletionProvider.GetCompletionDescriptions();
@@ -320,19 +327,21 @@ class GD_CORE_API ExpressionCompletionFinder
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, "", searchedPosition + 1, searchedPosition + 1));
type, "", searchedPosition + 1, searchedPosition + 1));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, "", searchedPosition + 1, searchedPosition + 1));
type, "", searchedPosition + 1, searchedPosition + 1));
}
void OnVisitOperatorNode(OperatorNode& node) override {
// No completions.
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type, "", searchedPosition + 1, searchedPosition + 1));
type, "", searchedPosition + 1, searchedPosition + 1));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type, "", searchedPosition + 1, searchedPosition + 1));
type, "", searchedPosition + 1, searchedPosition + 1));
}
void OnVisitNumberNode(NumberNode& node) override {
// No completions
@@ -344,6 +353,7 @@ class GD_CORE_API ExpressionCompletionFinder
FunctionCallNode* functionCall =
dynamic_cast<FunctionCallNode*>(maybeParentNodeAtLocation);
if (functionCall != nullptr) {
int parameterIndex = -1;
for (int i = 0; i < functionCall->parameters.size(); i++) {
if (functionCall->parameters.at(i).get() == &node) {
@@ -359,15 +369,16 @@ class GD_CORE_API ExpressionCompletionFinder
size_t metadataParameterIndex =
ExpressionParser2::WrittenParametersFirstIndex(
functionCall->objectName, functionCall->behaviorName);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, *functionCall);
const gd::ParameterMetadata* parameterMetadata = nullptr;
while (metadataParameterIndex <
functionCall->expressionMetadata.parameters.size()) {
if (!functionCall->expressionMetadata.parameters[metadataParameterIndex]
metadata.parameters.size()) {
if (!metadata.parameters[metadataParameterIndex]
.IsCodeOnly()) {
if (visibleParameterIndex == parameterIndex) {
parameterMetadata = &functionCall->expressionMetadata
.parameters[metadataParameterIndex];
parameterMetadata = &metadata.parameters[metadataParameterIndex];
}
visibleParameterIndex++;
}
@@ -398,12 +409,21 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitVariableNode(VariableNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
globalObjectsContainer,
objectsContainer,
// Variable fields doesn't use expression completion,
// so the object will be found inside the expression itself.
"",
node);
completions.push_back(ExpressionCompletionDescription::ForVariable(
node.type,
type,
node.name,
node.location.GetStartPosition(),
node.location.GetEndPosition(),
node.objectName));
objectName));
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
// No completions
@@ -413,35 +433,69 @@ class GD_CORE_API ExpressionCompletionFinder
// No completions
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (gd::ParameterMetadata::IsObject(node.type)) {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
// Only show completions of objects if an object is required
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
globalObjectsContainer,
objectsContainer,
// Variable fields doesn't use expression completion,
// so the object will be found inside the expression itself.
"",
node);
completions.push_back(ExpressionCompletionDescription::ForVariable(
type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition(),
objectName));
} else {
// Show completions for expressions and objects otherwise.
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
node.identifierName,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
// Object function or behavior name
if (IsCaretOn(node.identifierNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
type,
node.identifierName,
node.identifierNameLocation.GetStartPosition(),
node.identifierNameLocation.GetEndPosition()));
if (!node.identifierNameDotLocation.IsValid()) {
completions.push_back(ExpressionCompletionDescription::ForExpression(
type,
node.identifierName,
node.identifierNameLocation.GetStartPosition(),
node.identifierNameLocation.GetEndPosition()));
}
} else if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForBehavior(
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
node.identifierName));
completions.push_back(ExpressionCompletionDescription::ForExpression(
type,
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
node.identifierName));
}
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
if (!node.behaviorFunctionName.empty() ||
node.behaviorNameNamespaceSeparatorLocation.IsValid()) {
// Behavior function (or behavior function being written, with the
// function name missing)
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
@@ -455,7 +509,7 @@ class GD_CORE_API ExpressionCompletionFinder
} else if (IsCaretOn(node.behaviorNameNamespaceSeparatorLocation) ||
IsCaretOn(node.behaviorFunctionNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
type,
node.behaviorFunctionName,
node.behaviorFunctionNameLocation.GetStartPosition(),
node.behaviorFunctionNameLocation.GetEndPosition(),
@@ -466,7 +520,7 @@ class GD_CORE_API ExpressionCompletionFinder
// Object function or behavior name
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
@@ -478,7 +532,7 @@ class GD_CORE_API ExpressionCompletionFinder
node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
node.objectName));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
type,
node.objectFunctionOrBehaviorName,
node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
@@ -487,6 +541,7 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
bool isCaretOnParenthesis = IsCaretOn(node.openingParenthesisLocation) ||
IsCaretOn(node.closingParenthesisLocation);
@@ -494,7 +549,7 @@ class GD_CORE_API ExpressionCompletionFinder
// Behavior function
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
@@ -507,7 +562,7 @@ class GD_CORE_API ExpressionCompletionFinder
node.objectName));
} else {
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
type,
node.functionName,
node.functionNameLocation.GetStartPosition(),
node.functionNameLocation.GetEndPosition(),
@@ -519,7 +574,7 @@ class GD_CORE_API ExpressionCompletionFinder
// Object function
if (IsCaretOn(node.objectNameLocation)) {
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
type,
node.objectName,
node.objectNameLocation.GetStartPosition(),
node.objectNameLocation.GetEndPosition()));
@@ -538,7 +593,7 @@ class GD_CORE_API ExpressionCompletionFinder
}
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
type,
node.functionName,
node.functionNameLocation.GetStartPosition(),
node.functionNameLocation.GetEndPosition(),
@@ -548,7 +603,7 @@ class GD_CORE_API ExpressionCompletionFinder
} else {
// Free function
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
type,
node.functionName,
node.functionNameLocation.GetStartPosition(),
node.functionNameLocation.GetEndPosition())
@@ -556,13 +611,14 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
completions.push_back(ExpressionCompletionDescription::ForObject(
node.type,
type,
node.text,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
completions.push_back(ExpressionCompletionDescription::ForExpression(
node.type,
type,
node.text,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
@@ -578,14 +634,27 @@ class GD_CORE_API ExpressionCompletionFinder
(inclusive && searchedPosition <= location.GetEndPosition())));
}
ExpressionCompletionFinder(size_t searchedPosition_,
ExpressionCompletionFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String &rootType_,
size_t searchedPosition_,
gd::ExpressionNode* maybeParentNodeAtLocation_)
: searchedPosition(searchedPosition_),
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
rootType(rootType_),
searchedPosition(searchedPosition_),
maybeParentNodeAtLocation(maybeParentNodeAtLocation_){};
std::vector<ExpressionCompletionDescription> completions;
size_t searchedPosition;
gd::ExpressionNode* maybeParentNodeAtLocation;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String rootType;
};
} // namespace gd

View File

@@ -0,0 +1,123 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONLEFTSIDETYPEFINDER_H
#define GDCORE_EXPRESSIONLEFTSIDETYPEFINDER_H
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
} // namespace gd
namespace gd {
/**
* \brief Find the type of the node at the left side of operations.
*
* \see gd::ExpressionTypeFinder
*/
class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWorker {
public:
/**
* \brief Helper function to find the type of the node at the left side of
* operations.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
gd::ExpressionNode& node) {
gd::ExpressionLeftSideTypeFinder typeFinder(
platform, globalObjectsContainer, objectsContainer);
node.Visit(typeFinder);
return typeFinder.GetType();
}
virtual ~ExpressionLeftSideTypeFinder(){};
protected:
ExpressionLeftSideTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
type("unknown") {};
const gd::String &GetType() {
return type;
};
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
node.expression->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {
type = "number";
}
void OnVisitTextNode(TextNode& node) override {
type = "string";
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
type = "unknown";
}
else {
type = metadata.GetReturnType();
}
}
void OnVisitVariableNode(VariableNode& node) override {
type = "unknown";
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
type = "unknown";
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
type = "unknown";
}
void OnVisitEmptyNode(EmptyNode& node) override {
type = "unknown";
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
type = "unknown";
}
private:
gd::String type;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String rootType;
};
} // namespace gd
#endif // GDCORE_EXPRESSIONLEFTSIDETYPEFINDER_H

View File

@@ -0,0 +1,35 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
using namespace std;
namespace gd {
// TODO factorize in a file with an enum and helpers?
const gd::String ExpressionTypeFinder::unknownType = "unknown";
const gd::String ExpressionTypeFinder::numberType = "number";
const gd::String ExpressionTypeFinder::stringType = "string";
const gd::String ExpressionTypeFinder::numberOrStringType = "number|string";
} // namespace gd

View File

@@ -0,0 +1,198 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONTYPEFINDER_H
#define GDCORE_EXPRESSIONTYPEFINDER_H
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/IDE/Events/ExpressionLeftSideTypeFinder.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
} // namespace gd
namespace gd {
/**
* \brief Find the type of the expression or sub-expression that a given node
* represents.
*
* The type returned by this worker is a mix of:
* - an expected type looking up like a parameter declaration
* - an actual type looking down, but only looking at the most left branch
* (using ExpressionLeftSideTypeFinder)
*
* This logic was built with the constraint of following a parser that can't
* know the right side. Now that it is extracted, it could be enhanced if needed.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
public:
/**
* \brief Helper function to find the type of the expression or
* sub-expression that a given node represents.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionTypeFinder typeFinder(
platform, globalObjectsContainer, objectsContainer, rootType);
node.Visit(typeFinder);
return typeFinder.GetType();
}
virtual ~ExpressionTypeFinder(){};
protected:
ExpressionTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String &rootType_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
rootType(rootType_),
type(ExpressionTypeFinder::unknownType),
child(nullptr) {};
const gd::String &GetType() {
return gd::ParameterMetadata::GetExpressionValueType(type);
};
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
VisitParent(node);
}
void OnVisitOperatorNode(OperatorNode& node) override {
VisitParent(node);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
VisitParent(node);
}
void OnVisitNumberNode(NumberNode& node) override {
type = ExpressionTypeFinder::numberType;
}
void OnVisitTextNode(TextNode& node) override {
type = ExpressionTypeFinder::stringType;
}
void OnVisitVariableNode(VariableNode& node) override {
VisitParent(node);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
VisitParent(node);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
VisitParent(node);
}
void OnVisitEmptyNode(EmptyNode& node) override {
VisitParent(node);
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
VisitParent(node);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
if (child == nullptr) {
type = ExpressionTypeFinder::unknownType;
}
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
globalObjectsContainer,
objectsContainer,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
type = leftSideType;
}
else {
type = ExpressionTypeFinder::numberOrStringType;
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (child == nullptr) {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
VisitParent(node);
}
else {
type = metadata.GetReturnType();
}
}
else {
const gd::ParameterMetadata* parameterMetadata =
gd::MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
node,
*child);
if (parameterMetadata == nullptr || parameterMetadata->GetType().empty()) {
type = ExpressionTypeFinder::unknownType;
}
else {
type = parameterMetadata->GetType();
}
}
}
private:
inline void VisitParent(ExpressionNode& node) {
child = &node;
if (node.parent != nullptr) {
node.parent->Visit(*this);
}
else if (rootType == ExpressionTypeFinder::numberOrStringType) {
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
globalObjectsContainer,
objectsContainer,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
type = leftSideType;
}
else {
type = rootType;
}
}
else {
type = rootType;
}
}
static const gd::String unknownType;
static const gd::String numberType;
static const gd::String stringType;
static const gd::String numberOrStringType;
gd::String type;
ExpressionNode *child;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String rootType;
};
} // namespace gd
#endif // GDCORE_EXPRESSIONTYPEFINDER_H

View File

@@ -0,0 +1,300 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
using namespace std;
namespace gd {
namespace {
/**
* Return the minimum number of parameters, starting from a given parameter
* (by convention, 1 for object functions and 2 for behavior functions).
*/
size_t GetMinimumParametersNumber(
const std::vector<gd::ParameterMetadata>& parameters,
size_t initialParameterIndex) {
size_t nb = 0;
for (std::size_t i = initialParameterIndex; i < parameters.size(); ++i) {
if (!parameters[i].optional && !parameters[i].codeOnly) nb++;
}
return nb;
}
/**
* Return the maximum number of parameters, starting from a given parameter
* (by convention, 1 for object functions and 2 for behavior functions).
*/
size_t GetMaximumParametersNumber(
const std::vector<gd::ParameterMetadata>& parameters,
size_t initialParameterIndex) {
size_t nb = 0;
for (std::size_t i = initialParameterIndex; i < parameters.size(); ++i) {
if (!parameters[i].codeOnly) nb++;
}
return nb;
}
} // namespace
ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::FunctionCallNode& function) {
ReportAnyError(function);
gd::String objectType = function.objectName.empty() ? "" :
GetTypeOfObject(globalObjectsContainer, objectsContainer, function.objectName);
gd::String behaviorType = function.behaviorName.empty() ? "" :
GetTypeOfBehavior(globalObjectsContainer, objectsContainer, function.behaviorName);
const gd::ExpressionMetadata &metadata = function.behaviorName.empty() ?
function.objectName.empty() ?
MetadataProvider::GetAnyExpressionMetadata(platform, function.functionName) :
MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, function.functionName) :
MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, function.functionName);
if (!function.objectName.empty()) {
// If the function needs a capability on the object that may not be covered
// by all objects, check it now.
if (!metadata.GetRequiredBaseObjectCapability().empty()) {
const gd::ObjectMetadata &objectMetadata =
MetadataProvider::GetObjectMetadata(platform, objectType);
if (objectMetadata.IsUnsupportedBaseObjectCapability(
metadata.GetRequiredBaseObjectCapability())) {
RaiseTypeError(
_("This expression exists, but it can't be used on this object."),
function.objectNameLocation);
return StringToType(metadata.GetReturnType());
}
}
}
Type returnType = StringToType(metadata.GetReturnType());
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
RaiseError(
"invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
return returnType;
}
// Validate the type of the function
if (returnType == Type::Number) {
if (parentType == Type::String) {
RaiseTypeError(
_("You tried to use an expression that returns a number, but a "
"string is expected. Use `ToString` if you need to convert a "
"number to a string."),
function.location);
return returnType;
}
else if (parentType != Type::Number && parentType != Type::NumberOrString) {
RaiseTypeError(_("You tried to use an expression that returns a "
"number, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
return returnType;
}
} else if (returnType == Type::String) {
if (parentType == Type::Number) {
RaiseTypeError(
_("You tried to use an expression that returns a string, but a "
"number is expected. Use `ToNumber` if you need to convert a "
"string to a number."),
function.location);
return returnType;
}
else if (parentType != Type::String && parentType != Type::NumberOrString) {
RaiseTypeError(_("You tried to use an expression that returns a "
"string, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
return returnType;
}
} else {
if (parentType != returnType) {
RaiseTypeError(
_("You tried to use an expression with the wrong return type:") + " " +
TypeToString(returnType),
function.location);
return returnType;
}
}
// Validate parameters count
size_t minParametersCount = GetMinimumParametersNumber(
metadata.parameters,
ExpressionParser2::WrittenParametersFirstIndex(function.objectName, function.behaviorName));
size_t maxParametersCount = GetMaximumParametersNumber(
metadata.parameters,
ExpressionParser2::WrittenParametersFirstIndex(function.objectName, function.behaviorName));
if (function.parameters.size() < minParametersCount ||
function.parameters.size() > maxParametersCount) {
gd::String expectedCountMessage =
minParametersCount == maxParametersCount
? _("The number of parameters must be exactly ") +
gd::String::From(minParametersCount)
: _("The number of parameters must be: ") +
gd::String::From(minParametersCount) + "-" +
gd::String::From(maxParametersCount);
if (function.parameters.size() < minParametersCount) {
RaiseError(
"too_few_parameters",
_("You have not entered enough parameters for the expression.") + " " +
expectedCountMessage,
function.location);
}
else {
RaiseError(
"extra_parameter",
_("This parameter was not expected by this expression. Remove it "
"or verify that you've entered the proper expression name.") + " " +
expectedCountMessage,
ExpressionParserLocation(
function.parameters[maxParametersCount]->location.GetStartPosition(),
function.location.GetEndPosition() - 1));
}
return returnType;
}
// TODO: reverse the order of diagnostic?
size_t writtenParametersFirstIndex =
ExpressionParser2::WrittenParametersFirstIndex(
function.objectName, function.behaviorName);
int metadataIndex = writtenParametersFirstIndex;
for (int parameterIndex = 0; parameterIndex < function.parameters.size(); parameterIndex++) {
auto& parameter = function.parameters[parameterIndex];
while (metadata.GetParameters()[metadataIndex].IsCodeOnly()) {
// The sizes are already checked above.
metadataIndex++;
}
auto& parameterMetadata = metadata.GetParameters()[metadataIndex];
if (!parameterMetadata.IsOptional() || dynamic_cast<EmptyNode*>(parameter.get()) == nullptr) {
auto currentParentType = parentType;
parentType = StringToType(parameterMetadata.GetType());
parameter->Visit(*this);
parentType = currentParentType;
const gd::String &expectedParameterType = parameterMetadata.GetType();
if (gd::ParameterMetadata::IsExpression(
ExpressionValidator::variableTypeString, expectedParameterType)) {
if (dynamic_cast<IdentifierNode *>(parameter.get()) == nullptr
&& dynamic_cast<VariableNode *>(parameter.get()) == nullptr) {
RaiseError(
"malformed_variable_parameter",
_("A variable name was expected but something else was "
"written. Enter just the name of the variable for this "
"parameter."),
parameter->location);
}
}
else if (gd::ParameterMetadata::IsObject(expectedParameterType)) {
if (dynamic_cast<IdentifierNode *>(parameter.get()) == nullptr) {
RaiseError(
"malformed_object_parameter",
_("An object name was expected but something else was "
"written. Enter just the name of the object for this "
"parameter."),
parameter->location);
}
}
// String and number are already checked in children.
else if (!gd::ParameterMetadata::IsExpression(
ExpressionValidator::numberTypeString, expectedParameterType)
&& !gd::ParameterMetadata::IsExpression(
ExpressionValidator::stringTypeString, expectedParameterType)) {
RaiseError(
"unknown_parameter_type",
_("This function is improperly set up. Reach out to the "
"extension developer or a GDevelop maintainer to fix "
"this issue"),
parameter->location);
}
}
metadataIndex++;
}
return returnType;
}
// TODO factorize in a file with an enum and helpers?
const gd::String ExpressionValidator::unknownTypeString = "unknown";
const gd::String ExpressionValidator::numberTypeString = "number";
const gd::String ExpressionValidator::stringTypeString = "string";
const gd::String ExpressionValidator::numberOrStringTypeString = "number|string";
const gd::String ExpressionValidator::variableTypeString = "variable";
const gd::String ExpressionValidator::objectTypeString = "object";
const gd::String ExpressionValidator::emptyTypeString = "empty";
const gd::String &ExpressionValidator::TypeToString(Type type) {
switch (type) {
case Type::Unknown:
return unknownTypeString;
case Type::Number:
return numberTypeString;
case Type::String:
return stringTypeString;
case Type::NumberOrString:
return numberOrStringTypeString;
case Type::Variable:
return variableTypeString;
case Type::Object:
return objectTypeString;
case Type::Empty:
return emptyTypeString;
}
return unknownTypeString;
}
ExpressionValidator::Type ExpressionValidator::StringToType(const gd::String &type) {
if (type == ExpressionValidator::numberTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::numberTypeString, type)) {
return Type::Number;
}
if (type == ExpressionValidator::stringTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::stringTypeString, type)) {
return Type::String;
}
if (type == ExpressionValidator::numberOrStringTypeString) {
return Type::NumberOrString;
}
if (type == ExpressionValidator::variableTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::variableTypeString, type)) {
return Type::Variable;
}
if (type == ExpressionValidator::objectTypeString
|| gd::ParameterMetadata::IsObject(type)) {
return Type::Object;
}
return Type::Unknown;
}
} // namespace gd

View File

@@ -10,6 +10,10 @@
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
namespace gd {
class Expression;
class ObjectsContainer;
@@ -28,15 +32,27 @@ namespace gd {
*/
class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
public:
ExpressionValidator(){};
ExpressionValidator(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String &rootType_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
parentType(StringToType(gd::ParameterMetadata::GetExpressionValueType(rootType_))),
childType(Type::Unknown) {};
virtual ~ExpressionValidator(){};
/**
* \brief Helper function to check if a given node does not contain
* any error.
*/
static bool HasNoErrors(gd::ExpressionNode& node) {
gd::ExpressionValidator validator;
static bool HasNoErrors(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionValidator validator(platform, globalObjectsContainer, objectsContainer, rootType);
node.Visit(validator);
return validator.GetErrors().empty();
}
@@ -56,52 +72,247 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
ReportAnyError(node);
node.leftHandSide->Visit(*this);
const Type leftType = childType;
if (leftType == Type::Number) {
if (node.op == ' ') {
RaiseError("syntax_error",
"No operator found. Did you forget to enter an operator (like +, -, "
"* or /) between numbers or expressions?", node.rightHandSide->location);
}
}
else if (leftType == Type::String) {
if (node.op == ' ') {
RaiseError("syntax_error",
"You must add the operator + between texts or expressions. For "
"example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
}
else if (node.op != '+') {
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
}
} else if (leftType == Type::Object) {
RaiseOperatorError(
_("Operators (+, -, /, *) can't be used with an object name. Remove "
"the operator."),
node.rightHandSide->location);
} else if (leftType == Type::Variable) {
RaiseOperatorError(
_("Operators (+, -, /, *) can't be used in variable names. Remove "
"the operator from the variable name."),
node.rightHandSide->location);
}
parentType = leftType;
node.rightHandSide->Visit(*this);
const Type rightType = childType;
childType = leftType;
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
ReportAnyError(node);
node.factor->Visit(*this);
const Type rightType = childType;
if (rightType == Type::Number) {
if (node.op != '+' && node.op != '-') {
// This is actually a dead code because the parser takes them as
// binary operations with an empty left side which makes as much sense.
RaiseTypeError(
_("You've used an \"unary\" operator that is not supported. Operator "
"should be "
"either + or -."),
node.location);
}
} else if (rightType == Type::String) {
RaiseTypeError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts, and must be placed between two texts (or "
"expressions)."),
node.location);
} else if (rightType == Type::Object) {
RaiseTypeError(
_("Operators (+, -) can't be used with an object name. Remove the "
"operator."),
node.location);
} else if (rightType == Type::Variable) {
RaiseTypeError(
_("Operators (+, -) can't be used in variable names. Remove "
"the operator from the variable name."),
node.location);
}
}
void OnVisitNumberNode(NumberNode& node) override {
ReportAnyError(node);
childType = Type::Number;
CheckType(parentType, childType, node.location);
}
void OnVisitTextNode(TextNode& node) override {
ReportAnyError(node);
childType = Type::String;
CheckType(parentType, childType, node.location);
}
void OnVisitNumberNode(NumberNode& node) override { ReportAnyError(node); }
void OnVisitTextNode(TextNode& node) override { ReportAnyError(node); }
void OnVisitVariableNode(VariableNode& node) override {
ReportAnyError(node);
if (node.child) node.child->Visit(*this);
if (node.child) {
node.child->Visit(*this);
}
childType = Type::Variable;
CheckType(parentType, childType, node.location);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
ReportAnyError(node);
if (node.child) node.child->Visit(*this);
if (node.child) {
node.child->Visit(*this);
}
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
ReportAnyError(node);
Type currentParentType = parentType;
parentType = Type::NumberOrString;
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
parentType = currentParentType;
if (node.child) {
node.child->Visit(*this);
}
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
ReportAnyError(node);
if (parentType == Type::String) {
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
node.location);
}
else if (parentType == Type::Number) {
RaiseTypeError(
_("You must enter a number."), node.location);
}
else if (parentType == Type::NumberOrString) {
RaiseTypeError(
_("You must enter a number or a text, wrapped inside double quotes "
"(example: \"Hello world\")."),
node.location);
}
else if (parentType != Type::Object && parentType != Type::Variable) {
// It can't happen.
RaiseTypeError(
_("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
node.location);
}
childType = parentType;
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
ReportAnyError(node);
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
ReportAnyError(node);
for (auto& parameter : node.parameters) {
parameter->Visit(*this);
}
childType = ValidateFunction(node);
}
void OnVisitEmptyNode(EmptyNode& node) override {
ReportAnyError(node);
gd::String message;
if (parentType == Type::Number) {
message = _("You must enter a number or a valid expression call.");
} else if (parentType == Type::String) {
message = _(
"You must enter a text (between quotes) or a valid expression call.");
} else if (parentType == Type::Variable) {
message = _("You must enter a variable name.");
} else if (parentType == Type::Object) {
message = _("You must enter a valid object name.");
} else {
// It can't happen.
message = _("You must enter a valid expression.");
}
RaiseTypeError(message, node.location);
childType = Type::Empty;
}
void OnVisitEmptyNode(EmptyNode& node) override { ReportAnyError(node); }
private:
void ReportAnyError(ExpressionNode& node) {
enum Type {Unknown = 0, Number, String, NumberOrString, Variable, Object, Empty};
Type ValidateFunction(const gd::FunctionCallNode& function);
void ReportAnyError(const ExpressionNode& node) {
if (node.diagnostic && node.diagnostic->IsError()) {
// Syntax errors are holden by the AST nodes.
// It's fine to give pointers on them as the AST live longer than errors
// handling.
errors.push_back(node.diagnostic.get());
}
}
void RaiseError(const gd::String &type,
const gd::String &message, const ExpressionParserLocation &location) {
auto diagnostic = gd::make_unique<ExpressionParserError>(
type, message, location);
errors.push_back(diagnostic.get());
// Errors found by the validator are not holden by the AST nodes.
// They must be owned by the validator to keep living while errors are
// handled by the caller.
supplementalErrors.push_back(std::move(diagnostic));
}
void RaiseTypeError(
const gd::String &message, const ExpressionParserLocation &location) {
RaiseError("type_error", message, location);
}
void RaiseOperatorError(
const gd::String &message, const ExpressionParserLocation &location) {
RaiseError("invalid_operator", message, location);
}
void CheckType(Type expect, Type actual, const ExpressionParserLocation &location) {
if (actual == Type::String) {
if (expect == Type::Number) {
RaiseTypeError(_("You entered a text, but a number was expected."),
location);
}
else if (expect != Type::String && expect != Type::NumberOrString) {
RaiseTypeError(
_("You entered a text, but this type was expected:") + " " + TypeToString(expect),
location);
}
}
else if (actual == Type::Number) {
if (expect == Type::String) {
RaiseTypeError(
_("You entered a number, but a text was expected (in quotes)."),
location);
}
else if (expect != Type::Number && expect != Type::NumberOrString) {
RaiseTypeError(
_("You entered a number, but this type was expected:") + " " + TypeToString(expect),
location);
}
}
}
static Type StringToType(const gd::String &type);
static const gd::String &TypeToString(Type type);
static const gd::String unknownTypeString;
static const gd::String numberTypeString;
static const gd::String stringTypeString;
static const gd::String numberOrStringTypeString;
static const gd::String variableTypeString;
static const gd::String objectTypeString;
static const gd::String identifierTypeString;
static const gd::String emptyTypeString;
std::vector<ExpressionParserDiagnostic*> errors;
std::vector<std::unique_ptr<ExpressionParserDiagnostic>> supplementalErrors;
Type childType;
Type parentType;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
};
} // namespace gd

View File

@@ -0,0 +1,172 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXPRESSIONVARIABLEOWNERFINDER_H
#define GDCORE_EXPRESSIONVARIABLEOWNERFINDER_H
#include <memory>
#include <vector>
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Tools/Localization.h"
namespace gd {
class Expression;
class ObjectsContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
} // namespace gd
namespace gd {
/**
* \brief Find the object name that should be used as a context of the
* expression or sub-expression that a given node represents.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWorker {
public:
/**
* \brief Helper function to find the object name that should be used as a
* context of the expression or sub-expression that a given node represents.
*/
static const gd::String GetObjectName(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::String& rootObjectName,
gd::ExpressionNode& node) {
gd::ExpressionVariableOwnerFinder typeFinder(
platform, globalObjectsContainer, objectsContainer, rootObjectName);
node.Visit(typeFinder);
return typeFinder.GetObjectName();
}
virtual ~ExpressionVariableOwnerFinder(){};
protected:
ExpressionVariableOwnerFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::String& rootObjectName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
rootObjectName(rootObjectName_),
objectName(""),
variableNode(nullptr) {};
/**
* \brief Get all the errors
*
* No errors means that the expression is valid.
*/
const gd::String &GetObjectName() {
return objectName;
};
void OnVisitSubExpressionNode(SubExpressionNode& node) override {}
void OnVisitOperatorNode(OperatorNode& node) override {}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
if (variableNode != nullptr) {
// This is not possible
return;
}
if (node.parent == nullptr) {
objectName = rootObjectName;
return;
}
variableNode = &node;
node.parent->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
if (variableNode != nullptr) {
// This is not possible
return;
}
if (node.parent == nullptr) {
objectName = rootObjectName;
return;
}
// This node is not necessarily a variable node.
// It will be checked when visiting the FunctionCallNode.
variableNode = &node;
node.parent->Visit(*this);
}
void OnVisitEmptyNode(EmptyNode& node) override {}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& functionCall) override {
if (variableNode == nullptr) {
return;
}
int parameterIndex = -1;
for (int i = 0; i < functionCall.parameters.size(); i++) {
if (functionCall.parameters.at(i).get() == variableNode) {
parameterIndex = i;
break;
}
}
if (parameterIndex < 0) {
return;
}
const gd::ParameterMetadata* parameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
functionCall,
parameterIndex);
if (parameterMetadata == nullptr
|| parameterMetadata->GetType() != "objectvar") {
return;
}
// The object on which the function is called is returned if no previous
// parameters are objects.
objectName = functionCall.objectName;
for (int previousIndex = parameterIndex - 1; previousIndex >= 0; previousIndex--) {
const gd::ParameterMetadata* previousParameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
functionCall,
previousIndex);
if (previousParameterMetadata != nullptr
&& gd::ParameterMetadata::IsObject(previousParameterMetadata->GetType())) {
auto previousParameterNode = functionCall.parameters[previousIndex].get();
IdentifierNode* objectNode = dynamic_cast<IdentifierNode*>(previousParameterNode);
objectName = objectNode->identifierName;
return;
}
}
}
private:
gd::String objectName;
gd::ExpressionNode *variableNode;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::String &rootObjectName;
};
} // namespace gd
#endif // GDCORE_EXPRESSIONVARIABLEOWNERFINDER_H

View File

@@ -11,7 +11,6 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
@@ -146,17 +145,9 @@ bool ExpressionsParameterMover::DoVisitInstruction(gd::Instruction& instruction,
pNb < instruction.GetParametersCount();
++pNb) {
const gd::String& type = metadata.parameters[pNb].type;
const gd::String& expression =
instruction.GetParameter(pNb).GetPlainString();
const gd::Expression& expression = instruction.GetParameter(pNb);
gd::ExpressionParser2 parser(
platform, GetGlobalObjectsContainer(), GetObjectsContainer());
auto node = gd::ParameterMetadata::IsExpression("number", type)
? parser.ParseExpression("number", expression)
: (gd::ParameterMetadata::IsExpression("string", type)
? parser.ParseExpression("string", expression)
: std::unique_ptr<gd::ExpressionNode>());
auto node = expression.GetRootNode();
if (node) {
ExpressionParameterMover mover(GetGlobalObjectsContainer(),
GetObjectsContainer(),

View File

@@ -11,7 +11,6 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
@@ -156,18 +155,9 @@ bool ExpressionsRenamer::DoVisitInstruction(gd::Instruction& instruction,
for (std::size_t pNb = 0; pNb < metadata.parameters.size() &&
pNb < instruction.GetParametersCount();
++pNb) {
const gd::String& type = metadata.parameters[pNb].type;
const gd::String& expression =
instruction.GetParameter(pNb).GetPlainString();
const gd::Expression& expression = instruction.GetParameter(pNb);
gd::ExpressionParser2 parser(
platform, GetGlobalObjectsContainer(), GetObjectsContainer());
auto node = gd::ParameterMetadata::IsExpression("number", type)
? parser.ParseExpression("number", expression)
: (gd::ParameterMetadata::IsExpression("string", type)
? parser.ParseExpression("string", expression)
: std::unique_ptr<gd::ExpressionNode>());
auto node = expression.GetRootNode();
if (node) {
ExpressionFunctionRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),

View File

@@ -1,10 +1,10 @@
#include "UsedExtensionsFinder.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/BehaviorContent.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
@@ -54,13 +54,12 @@ bool UsedExtensionsFinder::DoVisitInstruction(gd::Instruction& instruction,
metadata.GetMetadata().GetParameter(i).GetType();
i++;
if (gd::ParameterMetadata::IsExpression("string", parameterType) ||
gd::ParameterMetadata::IsExpression("number", parameterType)) {
gd::ExpressionParser2 parser(project.GetCurrentPlatform(),
GetGlobalObjectsContainer(),
GetObjectsContainer());
parser.ParseExpression(parameterType, expression.GetPlainString())
->Visit(*this);
if (gd::ParameterMetadata::IsExpression("string", parameterType)) {
rootType = "string";
expression.GetRootNode()->Visit(*this);
} else if (gd::ParameterMetadata::IsExpression("number", parameterType)) {
rootType = "number";
expression.GetRootNode()->Visit(*this);
} else if (gd::ParameterMetadata::IsExpression("variable", parameterType))
usedExtensions.insert("BuiltinVariables");
}
@@ -113,7 +112,8 @@ void UsedExtensionsFinder::OnVisitVariableBracketAccessorNode(
// Add extensions bound to Objects/Behaviors/Functions
void UsedExtensionsFinder::OnVisitIdentifierNode(IdentifierNode& node) {
if (gd::ParameterMetadata::IsObject(node.type)) {
auto type = gd::ExpressionTypeFinder::GetType(project.GetCurrentPlatform(), GetGlobalObjectsContainer(), GetObjectsContainer(), rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
usedExtensions.insert(gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), node.identifierName)
.GetExtension()

View File

@@ -31,6 +31,7 @@ class GD_CORE_API UsedExtensionsFinder
private:
UsedExtensionsFinder(gd::Project& project_) : project(project_){};
gd::Project& project;
gd::String rootType;
std::set<gd::String> usedExtensions;
// Object Visitor

View File

@@ -18,7 +18,7 @@
namespace gd {
void EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::Project& project,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer) {
@@ -36,7 +36,7 @@ void EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
}
void EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,

View File

@@ -32,7 +32,7 @@ class GD_CORE_API EventsFunctionTools {
* generation for example.
*/
static void FreeEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::Project& project,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer);
@@ -46,7 +46,7 @@ class GD_CORE_API EventsFunctionTools {
* generation for example.
*/
static void BehaviorEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,

View File

@@ -126,9 +126,10 @@ class ResourceWorkerInEventsWorker : public ArbitraryEventsWorker {
instruction.GetParameters(),
metadata.GetParameters(),
[this, &instruction](const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::Expression& parameterExpression,
size_t parameterIndex,
const gd::String& lastObjectName) {
const String& parameterValue = parameterExpression.GetPlainString();
if (parameterMetadata.GetType() ==
"police") { // Should be renamed fontResource
gd::String updatedParameterValue = parameterValue;

View File

@@ -54,11 +54,8 @@ Layout::Layout()
oglFOV(90.0f),
oglZNear(1.0f),
oglZFar(500.0f),
disableInputWhenNotFocused(true)
#if defined(GD_IDE_ONLY)
,
disableInputWhenNotFocused(true),
profiler(NULL)
#endif
{
gd::Layer layer;
layer.SetCameraCount(1);
@@ -332,7 +329,6 @@ void Layout::UnserializeFrom(gd::Project& project,
disableInputWhenNotFocused =
element.GetBoolAttribute("disableInputWhenNotFocused");
#if defined(GD_IDE_ONLY)
editorSettings.UnserializeFrom(
element.GetChild("uiSettings", 0, "UISettings"));
@@ -340,7 +336,6 @@ void Layout::UnserializeFrom(gd::Project& project,
element.GetChild("objectsGroups", 0, "GroupesObjets"));
gd::EventsListSerialization::UnserializeEventsFrom(
project, GetEvents(), element.GetChild("events", 0, "Events"));
#endif
UnserializeObjectsFrom(project, element.GetChild("objects", 0, "Objets"));
initialInstances.UnserializeFrom(
@@ -410,13 +405,11 @@ void Layout::Init(const Layout& other) {
std::unique_ptr<gd::BehaviorContent>(it.second->Clone());
}
#if defined(GD_IDE_ONLY)
events = other.events;
editorSettings = other.editorSettings;
objectGroups = other.objectGroups;
profiler = other.profiler;
#endif
}
std::vector<gd::String> GetHiddenLayers(const Layout& layout) {
@@ -430,7 +423,6 @@ std::vector<gd::String> GetHiddenLayers(const Layout& layout) {
return hiddenLayers;
}
#if defined(GD_IDE_ONLY)
gd::String GD_CORE_API GetTypeOfObject(const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::String name,
@@ -444,7 +436,7 @@ gd::String GD_CORE_API GetTypeOfObject(const gd::ObjectsContainer& project,
type = project.GetObject(name).GetType();
// Search in groups
if (searchInGroups) {
else if (searchInGroups) {
for (std::size_t i = 0; i < layout.GetObjectGroups().size(); ++i) {
if (layout.GetObjectGroups()[i].GetName() == name) {
// A group has the name searched
@@ -505,18 +497,16 @@ gd::String GD_CORE_API GetTypeOfBehavior(const gd::ObjectsContainer& project,
gd::String name,
bool searchInGroups) {
for (std::size_t i = 0; i < layout.GetObjectsCount(); ++i) {
vector<gd::String> behaviors = layout.GetObject(i).GetAllBehaviorNames();
for (std::size_t j = 0; j < behaviors.size(); ++j) {
if (layout.GetObject(i).GetBehavior(behaviors[j]).GetName() == name)
return layout.GetObject(i).GetBehavior(behaviors[j]).GetTypeName();
const auto &object = layout.GetObject(i);
if (object.HasBehaviorNamed(name)) {
return object.GetBehavior(name).GetTypeName();
}
}
for (std::size_t i = 0; i < project.GetObjectsCount(); ++i) {
vector<gd::String> behaviors = project.GetObject(i).GetAllBehaviorNames();
for (std::size_t j = 0; j < behaviors.size(); ++j) {
if (project.GetObject(i).GetBehavior(behaviors[j]).GetName() == name)
return project.GetObject(i).GetBehavior(behaviors[j]).GetTypeName();
const auto &object = project.GetObject(i);
if (object.HasBehaviorNamed(name)) {
return object.GetBehavior(name).GetTypeName();
}
}
@@ -612,6 +602,5 @@ GetBehaviorsOfObject(const gd::ObjectsContainer& project,
return behaviors;
}
#endif
} // namespace gd

View File

@@ -12,9 +12,7 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
#if defined(GD_IDE_ONLY)
#include "GDCore/Project/PropertyDescriptor.h"
#endif
namespace gd {
@@ -24,6 +22,7 @@ Object::Object(const gd::String& name_) : name(name_) {}
void Object::Init(const gd::Object& object) {
name = object.name;
assetStoreId = object.assetStoreId;
type = object.type;
objectVariables = object.objectVariables;
tags = object.tags;
@@ -80,13 +79,12 @@ gd::BehaviorContent& Object::AddBehavior(
return *behaviors[behaviorName];
}
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> Object::GetProperties() const {
std::map<gd::String, gd::PropertyDescriptor> nothing;
return nothing;
}
gd::BehaviorContent* Object::AddNewBehavior(gd::Project& project,
gd::BehaviorContent* Object::AddNewBehavior(const gd::Project& project,
const gd::String& type,
const gd::String& name) {
const gd::BehaviorMetadata& behaviorMetadata =
@@ -109,11 +107,11 @@ Object::GetInitialInstanceProperties(const gd::InitialInstance& instance,
std::map<gd::String, gd::PropertyDescriptor> nothing;
return nothing;
}
#endif
void Object::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
type = element.GetStringAttribute("type");
assetStoreId = element.GetStringAttribute("assetStoreId");
name = element.GetStringAttribute("name", name, "nom");
tags = element.GetStringAttribute("tags");
@@ -184,9 +182,9 @@ void Object::UnserializeFrom(gd::Project& project,
DoUnserializeFrom(project, element);
}
#if defined(GD_IDE_ONLY)
void Object::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());
element.SetAttribute("assetStoreId", GetAssetStoreId());
element.SetAttribute("type", GetType());
element.SetAttribute("tags", GetTags());
objectVariables.SerializeTo(element.AddChild("variables"));
@@ -209,6 +207,5 @@ void Object::SerializeTo(SerializerElement& element) const {
DoSerializeTo(element);
}
#endif
} // namespace gd

View File

@@ -83,6 +83,14 @@ class GD_CORE_API Object {
*/
const gd::String& GetName() const { return name; };
/** \brief Change the asset store id of the object.
*/
void SetAssetStoreId(const gd::String& assetStoreId_) { assetStoreId = assetStoreId_; };
/** \brief Return the asset store id of the object.
*/
const gd::String& GetAssetStoreId() const { return assetStoreId; };
/** \brief Change the type of the object.
*/
void SetType(const gd::String& type_) { type = type_; }
@@ -100,7 +108,6 @@ class GD_CORE_API Object {
const gd::String& GetTags() const { return tags; }
///@}
#if defined(GD_IDE_ONLY)
/** \name Resources management
* Members functions related to managing resources used by the object
*/
@@ -186,7 +193,6 @@ class GD_CORE_API Object {
return false;
};
///@}
#endif
/** \name Behaviors management
* Members functions related to behaviors management.
@@ -232,7 +238,6 @@ class GD_CORE_API Object {
*/
gd::BehaviorContent& AddBehavior(const gd::BehaviorContent& behavior);
#if defined(GD_IDE_ONLY)
/**
* \brief Add the behavior of the specified \a type with the specified \a
* name.
@@ -242,10 +247,9 @@ class GD_CORE_API Object {
* \return A pointer to the newly added behavior content. NULL if the creation
* failed.
*/
gd::BehaviorContent* AddNewBehavior(gd::Project& project,
gd::BehaviorContent* AddNewBehavior(const gd::Project& project,
const gd::String& type,
const gd::String& name);
#endif
/**
* \brief Get a read-only access to the map containing the behaviors with
@@ -296,13 +300,11 @@ class GD_CORE_API Object {
* Members functions related to serialization of the object
*/
///@{
#if defined(GD_IDE_ONLY)
/**
* \brief Serialize the object.
* \see DoSerializeTo
*/
void SerializeTo(SerializerElement& element) const;
#endif
/**
* \brief Unserialize the object.
@@ -313,6 +315,7 @@ class GD_CORE_API Object {
protected:
gd::String name; ///< The full name of the object
gd::String assetStoreId; ///< The ID of the asset if the object comes from the store.
gd::String type; ///< Which type is the object. ( To test if we can do
///< something reserved to some objects with it )
std::map<gd::String, std::unique_ptr<gd::BehaviorContent>>
@@ -331,12 +334,10 @@ class GD_CORE_API Object {
virtual void DoUnserializeFrom(gd::Project& project,
const SerializerElement& element){};
#if defined(GD_IDE_ONLY)
/**
* \brief Derived objects can redefine this method to save custom attributes.
*/
virtual void DoSerializeTo(SerializerElement& element) const {};
#endif
/**
* Initialize object using another object. Used by copy-ctor and assign-op.

View File

@@ -76,7 +76,7 @@ std::size_t ObjectsContainer::GetObjectsCount() const {
return initialObjects.size();
}
#if defined(GD_IDE_ONLY)
gd::Object& ObjectsContainer::InsertNewObject(gd::Project& project,
gd::Object& ObjectsContainer::InsertNewObject(const gd::Project& project,
const gd::String& objectType,
const gd::String& name,
std::size_t position) {

View File

@@ -94,7 +94,7 @@ class GD_CORE_API ObjectsContainer {
* \note The object is created using the project's current platform.
* \return A reference to the object in the list.
*/
gd::Object& InsertNewObject(gd::Project& project,
gd::Object& InsertNewObject(const gd::Project& project,
const gd::String& objectType,
const gd::String& name,
std::size_t position);

View File

@@ -51,6 +51,7 @@ Project::Project()
: name(_("Project")),
version("1.0.0"),
packageName("com.example.gamename"),
templateSlug(""),
orientation("landscape"),
folderProject(false),
windowWidth(800),
@@ -516,6 +517,7 @@ void Project::UnserializeFrom(const SerializerElement& element) {
SetProjectUuid(propElement.GetStringAttribute("projectUuid", ""));
SetAuthor(propElement.GetChild("author", 0, "Auteur").GetValue().GetString());
SetPackageName(propElement.GetStringAttribute("packageName"));
SetTemplateSlug(propElement.GetStringAttribute("templateSlug"));
SetOrientation(propElement.GetStringAttribute("orientation", "default"));
SetFolderProject(propElement.GetBoolAttribute("folderProject"));
SetLastCompilationDirectory(propElement
@@ -747,6 +749,7 @@ void Project::SerializeTo(SerializerElement& element) const {
propElement.SetAttribute("projectUuid", projectUuid);
propElement.SetAttribute("folderProject", folderProject);
propElement.SetAttribute("packageName", packageName);
propElement.SetAttribute("templateSlug", templateSlug);
propElement.SetAttribute("orientation", orientation);
platformSpecificAssets.SerializeTo(
propElement.AddChild("platformSpecificAssets"));
@@ -974,6 +977,7 @@ void Project::Init(const gd::Project& game) {
isPlayableWithGamepad = game.isPlayableWithGamepad;
isPlayableWithMobile = game.isPlayableWithMobile;
packageName = game.packageName;
templateSlug = game.templateSlug;
orientation = game.orientation;
folderProject = game.folderProject;
latestCompilationDirectory = game.latestCompilationDirectory;

View File

@@ -162,6 +162,18 @@ class GD_CORE_API Project : public ObjectsContainer {
*/
const gd::String& GetPackageName() const { return packageName; }
/**
* \brief Change the slug of the template from which the project is created.
*/
void SetTemplateSlug(const gd::String& templateSlug_) {
templateSlug = templateSlug_;
};
/**
* \brief Get the slug of the template from which the project is created.
*/
const gd::String& GetTemplateSlug() const { return templateSlug; }
/**
* \brief Change the project orientation (in particular when exported with
* Cordova). This has no effect on desktop and web browsers. \param
@@ -983,6 +995,8 @@ class GD_CORE_API Project : public ObjectsContainer {
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
bool isPlayableWithMobile; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
gd::String templateSlug; ///< The slug of the template from which the game is
///< created.
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
bool

View File

@@ -184,11 +184,17 @@ std::map<gd::String, gd::PropertyDescriptor> AudioResource::GetProperties()
const {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Preload as sound")]
.SetDescription(_("Loads the fully decoded file into cache, so it can be played right away as Sound with no further delays."))
.SetValue(preloadAsSound ? "true" : "false")
.SetType("Boolean");
properties[_("Preload as music")]
.SetDescription(_("Prepares the file for immediate streaming as Music (does not wait for complete download)."))
.SetValue(preloadAsMusic ? "true" : "false")
.SetType("Boolean");
properties[_("Preload in cache")]
.SetDescription(_("Loads the complete file into cache, but does not decode it into memory until requested."))
.SetValue(preloadInCache ? "true" : "false")
.SetType("Boolean");
return properties;
}
@@ -199,6 +205,8 @@ bool AudioResource::UpdateProperty(const gd::String& name,
preloadAsSound = value == "1";
else if (name == _("Preload as music"))
preloadAsMusic = value == "1";
else if (name == _("Preload in cache"))
preloadInCache = value == "1";
return true;
}
@@ -569,6 +577,7 @@ void AudioResource::UnserializeFrom(const SerializerElement& element) {
SetFile(element.GetStringAttribute("file"));
SetPreloadAsMusic(element.GetBoolAttribute("preloadAsMusic"));
SetPreloadAsSound(element.GetBoolAttribute("preloadAsSound"));
SetPreloadInCache(element.GetBoolAttribute("preloadInCache"));
}
void AudioResource::SerializeTo(SerializerElement& element) const {
@@ -576,6 +585,7 @@ void AudioResource::SerializeTo(SerializerElement& element) const {
element.SetAttribute("file", GetFile());
element.SetAttribute("preloadAsMusic", PreloadAsMusic());
element.SetAttribute("preloadAsSound", PreloadAsSound());
element.SetAttribute("preloadInCache", PreloadInCache());
}
void FontResource::SetFile(const gd::String& newFile) {

View File

@@ -60,7 +60,7 @@ class GD_CORE_API Resource {
* \see gd::Resource::GetFile
* \see gd::Resource::SetFile
*/
virtual bool UseFile() { return false; }
virtual bool UseFile() const { return false; }
/**
* \brief Return, if applicable, the String containing the file used by the
@@ -184,7 +184,7 @@ class GD_CORE_API ImageResource : public Resource {
*/
virtual void SetFile(const gd::String& newFile) override;
virtual bool UseFile() override { return true; }
virtual bool UseFile() const override { return true; }
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
@@ -223,7 +223,7 @@ class GD_CORE_API ImageResource : public Resource {
*/
class GD_CORE_API AudioResource : public Resource {
public:
AudioResource() : Resource(), preloadAsMusic(false), preloadAsSound(false) {
AudioResource() : Resource(), preloadAsMusic(false), preloadAsSound(false), preloadInCache(false) {
SetKind("audio");
};
virtual ~AudioResource(){};
@@ -234,7 +234,7 @@ class GD_CORE_API AudioResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
virtual bool UseFile() override { return true; }
virtual bool UseFile() const override { return true; }
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
@@ -263,10 +263,21 @@ class GD_CORE_API AudioResource : public Resource {
*/
void SetPreloadAsSound(bool enable = true) { preloadAsSound = enable; }
/**
* \brief Return true if the audio resource should be preloaded in cache (without decoding into memory).
*/
bool PreloadInCache() const { return preloadInCache; }
/**
* \brief Set if the audio resource should be preloaded in cache (without decoding into memory).
*/
void SetPreloadInCache(bool enable = true) { preloadInCache = enable; }
private:
gd::String file;
bool preloadAsSound;
bool preloadAsMusic;
bool preloadInCache;
};
/**
@@ -286,7 +297,7 @@ class GD_CORE_API FontResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
virtual bool UseFile() override { return true; }
virtual bool UseFile() const override { return true; }
void SerializeTo(SerializerElement& element) const override;
void UnserializeFrom(const SerializerElement& element) override;
@@ -312,7 +323,7 @@ class GD_CORE_API VideoResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
virtual bool UseFile() override { return true; }
virtual bool UseFile() const override { return true; }
void SerializeTo(SerializerElement& element) const override;
void UnserializeFrom(const SerializerElement& element) override;
@@ -338,7 +349,7 @@ class GD_CORE_API JsonResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
virtual bool UseFile() override { return true; }
virtual bool UseFile() const override { return true; }
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
@@ -379,7 +390,7 @@ class GD_CORE_API BitmapFontResource : public Resource {
virtual const gd::String& GetFile() const override { return file; };
virtual void SetFile(const gd::String& newFile) override;
virtual bool UseFile() override { return true; }
virtual bool UseFile() const override { return true; }
void SerializeTo(SerializerElement& element) const override;
void UnserializeFrom(const SerializerElement& element) override;

View File

@@ -354,7 +354,7 @@ const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding )
}
else
{
while ( *p && IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' )
while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' )
++p;
}

View File

@@ -197,6 +197,17 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
.AddParameter("object", _("Object 2 parameter"))
.AddParameter("objectvar", _("Variable for object 2"))
.SetFunctionName("getStringWith2ObjectParamAnd2ObjectVarParam");
extension
->AddStrExpression(
"GetStringWith1ObjectParamAnd2ObjectVarParam",
"Get string with 2 objectvar param one from the same object param",
"",
"",
"")
.AddParameter("object", _("Object 1 parameter"))
.AddParameter("objectvar", _("Variable for object 1"))
.AddParameter("objectvar", _("Variable for object 2"))
.SetFunctionName("getStringWith1ObjectParamAnd2ObjectVarParam");
auto& object = extension->AddObject<gd::Object>(
"Sprite", "Dummy Sprite", "Dummy sprite object", "");

View File

@@ -32,7 +32,7 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
group.AddObject("MyOtherSpriteObject");
group.AddObject("MyFakeObjectWithUnsupportedCapability");
gd::ExpressionParser2 parser(platform, project, layout1);
gd::ExpressionParser2 parser;
unsigned int maxDepth = 0;
gd::EventsCodeGenerationContext context(&maxDepth);
@@ -40,8 +40,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
SECTION("Valid text generation") {
{
auto node = parser.ParseExpression("string", "\"hello world\"");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("\"hello world\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -49,8 +51,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello world\"");
}
{
auto node = parser.ParseExpression("string", "\"hello\" + \"world\" ");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("\"hello\" + \"world\" ");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -59,8 +63,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
{
auto node = parser.ParseExpression(
"string", "\"{\\\"hello\\\": \\\"world \\\\\\\" \\\"}\"");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
"\"{\\\"hello\\\": \\\"world \\\\\\\" \\\"}\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -72,8 +78,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
SECTION("Valid number generation") {
{
auto node = parser.ParseExpression("number", "12.45");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("12.45");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -85,8 +93,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
SECTION("Invalid operators generation") {
// TODO: Should any error return directly 0 or ""?
{
auto node = parser.ParseExpression("number", "12.45 +");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("12.45 +");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -94,8 +104,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() == "12.45 + 0");
}
{
auto node = parser.ParseExpression("number", "12.45 * *");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("12.45 * *");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -106,16 +118,20 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
SECTION("Valid unary operator generation") {
{
auto node = parser.ParseExpression("number", "-12.45");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("-12.45");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "-(12.45)");
}
{
auto node = parser.ParseExpression("number", "12.5 + -2. / (.3)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("12.5 + -2. / (.3)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
@@ -124,20 +140,24 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
SECTION("Valid function calls") {
{
SECTION("without parameter") {
auto node =
parser.ParseExpression("number", " 1 / MyExtension::GetNumber()");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
parser.ParseExpression(" 1 / MyExtension::GetNumber()");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "1 / getNumber()");
}
{
SECTION("number and string parameters") {
auto node = parser.ParseExpression(
"number", "MyExtension::GetNumberWith2Params(12, \"hello world\")");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
"MyExtension::GetNumberWith2Params(12, \"hello world\")");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -145,12 +165,13 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getNumberWith2Params(12, \"hello world\")");
}
{
SECTION("nested function call") {
auto node =
parser.ParseExpression("number",
"MyExtension::GetNumberWith2Params("
parser.ParseExpression("MyExtension::GetNumberWith2Params("
"MyExtension::GetNumber(), \"hello world\")");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -158,10 +179,12 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getNumberWith2Params(getNumber(), \"hello world\")");
}
{
SECTION("object function") {
auto node =
parser.ParseExpression("number", "MySpriteObject.GetObjectNumber()");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
parser.ParseExpression("MySpriteObject.GetObjectNumber()");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -169,11 +192,12 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() ==
"MySpriteObject.getObjectNumber() ?? 0");
}
{
SECTION("object function with nested free function") {
auto node = parser.ParseExpression(
"string",
"MySpriteObject.GetObjectStringWith1Param(MyExtension::GetNumber())");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -183,22 +207,11 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
}
SECTION("Valid function calls with optional arguments") {
{
auto node =
parser.ParseExpression("number", "MyExtension::MouseX(\"layer1\",)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getMouseX(\"\", \"layer1\", 0)");
// (first argument is the currentScene)
}
{
auto node = parser.ParseExpression("number",
"MyExtension::MouseX(\"layer1\",2+2)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
SECTION("with optional parameter set") {
auto node = parser.ParseExpression("MyExtension::MouseX(\"layer1\",2+2)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -207,13 +220,39 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"getMouseX(\"\", \"layer1\", 2 + 2)");
// (first argument is the currentScene)
}
}
SECTION(
"Valid function calls (deprecated way of specifying optional "
"arguments)") {
{
auto node = parser.ParseExpression("number", "MyExtension::MouseX(,)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
SECTION("with last optional parameter omit") {
auto node =
parser.ParseExpression("MyExtension::MouseX(\"layer1\")");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getMouseX(\"\", \"layer1\", 0)");
// (first argument is the currentScene)
}
SECTION("with last optional parameter omit (deprecated way)") {
auto node =
parser.ParseExpression("MyExtension::MouseX(\"layer1\",)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getMouseX(\"\", \"layer1\", 0)");
// (first argument is the currentScene)
}
SECTION("with explicit comma (deprecated way)") {
auto node = parser.ParseExpression("MyExtension::MouseX(,)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -223,15 +262,17 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
// (first argument is the currentScene)
}
}
SECTION("Invalid function calls") {
{
SECTION("unknown identifier in parameters") {
auto node =
parser.ParseExpression("string",
"MySpriteObject.GetObjectStringWith3Param("
parser.ParseExpression("MySpriteObject.GetObjectStringWith3Param("
"MySpriteObject.GetObjectNumber() / 2.3, "
"MySpriteObject.GetObjectStringWith1Param("
"MyExtension::GetNumber()), test)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -243,33 +284,38 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"/* Error during generation, unrecognized identifier type: "
"unknown with value test */ \"test\") ?? \"\"");
}
{
auto node = parser.ParseExpression(
"number",
"MyExtension::GetNumberWith2Params(MyExtension::GetNumber())");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
context);
SECTION("missing parameter") {
{
auto node = parser.ParseExpression(
"MyExtension::GetNumberWith2Params(MyExtension::GetNumber())");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getNumberWith2Params(getNumber(), /* Error during generation, "
"parameter not existing in the nodes */ \"\")");
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getNumberWith2Params(getNumber(), /* Error during generation, "
"parameter not existing in the nodes */ \"\")");
}
{
// Using GenerateExpressionCode, the default value of 0 should be returned
// as expression is invalid.
REQUIRE(
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
context,
"number",
"MyExtension::GetNumberWith2Params(MyExtension::GetNumber())") ==
"0");
}
}
{
// Using GenerateExpressionCode, the default value of 0 should be returned
// as expression is invalid.
REQUIRE(
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
context,
"number",
"MyExtension::GetNumberWith2Params(MyExtension::GetNumber())") ==
"0");
}
{
auto node = parser.ParseExpression("number", "MyExtension::Idontexist()");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
SECTION("unknown function") {
auto node = parser.ParseExpression("MyExtension::Idontexist()");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -278,11 +324,12 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"/* Error during generation, function not found: "
"MyExtension::Idontexist */ 0");
}
{
auto node = parser.ParseExpression("number",
"MyExtension::GetNumberWith2Params(1, "
SECTION("too much parameters") {
auto node = parser.ParseExpression("MyExtension::GetNumberWith2Params(1, "
"\"2\", MyExtension::GetNumber())");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -291,13 +338,14 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"getNumberWith2Params(1, \"2\")");
}
}
SECTION("Invalid function calls (capabilities)") {
{
SECTION("function calls (capabilities)") {
SECTION("supported capability") {
// Capability is supported, so the expression is valid.
auto node = parser.ParseExpression(
"string",
"MySpriteObject.GetSomethingRequiringEffectCapability(123)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -306,26 +354,29 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
expressionCodeGenerator.GetOutput() ==
"MySpriteObject.getSomethingRequiringEffectCapability(123) ?? \"\"");
}
{
SECTION("unsupported capability") {
// Capability is not supported, so the expression is not even valid.
auto node =
parser.ParseExpression("string",
"MyFakeObjectWithUnsupportedCapability."
parser.ParseExpression("MyFakeObjectWithUnsupportedCapability."
"GetSomethingRequiringEffectCapability(123)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"\"");
}
{
SECTION("group with partial support") {
// We use a group, capability is supported only by one object of the
// group. The expression itself is valid, but code generation should skip
// the objects with unsupported capability.
auto node = parser.ParseExpression(
"string", "AllObjects.GetSomethingRequiringEffectCapability(123)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
"AllObjects.GetSomethingRequiringEffectCapability(123)");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -336,51 +387,87 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"MySpriteObject.getSomethingRequiringEffectCapability(123) ?? \"\"");
}
}
SECTION("Function name") {
auto node =
parser.ParseExpression("MySpriteObject.GetObjectNumber");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "0");
}
SECTION("Invalid variables") {
{
// Test an empty expression
SECTION("empty variable") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "") == "fakeBadVariable");
}
{
// Test a unary operator
SECTION("only an unary operator") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "-") ==
"fakeBadVariable");
}
{
// Test an operator
SECTION("only a binary operator") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "globalvar", "/") ==
"fakeBadVariable");
}
}
SECTION("Invalid variables, using operators") {
{
// Test a unary operator
SECTION("unary operation") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "-(var1)") ==
"fakeBadVariable");
}
{
// Test an operator
SECTION("binary operation") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "globalvar", "var1+var2") ==
"fakeBadVariable");
}
{
// Test multiple operators
SECTION("multiple operation") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "globalvar", "var1/var2/var3/var4") ==
"fakeBadVariable");
}
}
SECTION("Valid variables") {
SECTION("simple variable") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable", "")
== "getLayoutVariable(myVariable)");
}
SECTION("child dot accessor") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable.myChild", "")
== "getLayoutVariable(myVariable).getChild(\"myChild\")");
}
SECTION("2 children") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable.child1.child2", "")
== "getLayoutVariable(myVariable).getChild(\"child1\").getChild(\"child2\")");
}
SECTION("bracket access") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "scenevar", "myVariable[ \"hello\" + "
"\"world\" ]", "")
== "getLayoutVariable(myVariable).getChild(\"hello\" + \"world\")");
}
SECTION("object variable") {
REQUIRE(gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator, context, "objectvar", "myVariable", "MySpriteObject")
== "getVariableForObject(MySpriteObject, myVariable)");
}
}
SECTION("Valid function calls with variables") {
SECTION("Simple access") {
{
SECTION("Scene variable") {
auto node = parser.ParseExpression(
"number", "MyExtension::GetVariableAsNumber(myVariable)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
"MyExtension::GetVariableAsNumber(myVariable)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -388,11 +475,12 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() ==
"returnVariable(getLayoutVariable(myVariable))");
}
{
SECTION("Global variable") {
auto node = parser.ParseExpression(
"number",
"MyExtension::GetGlobalVariableAsNumber(myGlobalVariable)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -400,13 +488,76 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(expressionCodeGenerator.GetOutput() ==
"returnVariable(getProjectVariable(myGlobalVariable))");
}
SECTION("Variables on different objects") {
auto node = parser.ParseExpression(
"MyExtension::GetStringWith2ObjectParamAnd2ObjectVarParam("
"MySpriteObject, myVariable, MyOtherSpriteObject, myOtherVariable)");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getStringWith2ObjectParamAnd2ObjectVarParam(fakeObjectListOf_MySpriteObject, "
"getVariableForObject(MySpriteObject, myVariable), "
"fakeObjectListOf_MyOtherSpriteObject, "
"getVariableForObject(MyOtherSpriteObject, myOtherVariable))");
}
SECTION("Variables on the same object") {
auto node = parser.ParseExpression(
"MyExtension::GetStringWith1ObjectParamAnd2ObjectVarParam("
"MySpriteObject, myVariable, myOtherVariable)");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"getStringWith1ObjectParamAnd2ObjectVarParam(fakeObjectListOf_MySpriteObject, "
"getVariableForObject(MySpriteObject, myVariable), "
"getVariableForObject(MySpriteObject, myOtherVariable))");
}
SECTION("Object variable with object function call") {
auto node = parser.ParseExpression(
"MySpriteObject.GetObjectVariableAsNumber(myVariable)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(
expressionCodeGenerator.GetOutput() ==
"MySpriteObject.returnVariable(getVariableForObject("
"MySpriteObject, myVariable)) ?? 0");
}
}
SECTION("Child access") {
{
SECTION("1 child") {
auto node = parser.ParseExpression(
"MyExtension::GetVariableAsNumber(myVariable.child1)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() ==
"returnVariable(getLayoutVariable(myVariable).getChild("
"\"child1\"))");
}
SECTION("2 children") {
auto node = parser.ParseExpression(
"number",
"MyExtension::GetVariableAsNumber(myVariable.child1.child2)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -415,12 +566,13 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"returnVariable(getLayoutVariable(myVariable).getChild("
"\"child1\").getChild(\"child2\"))");
}
{
SECTION("bracket access") {
auto node = parser.ParseExpression(
"number",
"MyExtension::GetVariableAsNumber(myVariable[ \"hello\" + "
"\"world\" ].child2)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -429,13 +581,14 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
"returnVariable(getLayoutVariable(myVariable).getChild("
"\"hello\" + \"world\").getChild(\"child2\"))");
}
{
SECTION("bracket access with nested variable") {
auto node = parser.ParseExpression(
"number",
"MyExtension::GetVariableAsNumber(myVariable[ \"hello\" + "
"MySpriteObject.GetObjectStringWith1Param(MyOtherSpriteObject."
"GetObjectVariableAsNumber(mySecondVariable)) ].child2)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
@@ -462,8 +615,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
SECTION("Mixed test (1)") {
{
auto node = parser.ParseExpression("number", "-+-MyExtension::MouseX(,)");
gd::ExpressionCodeGenerator expressionCodeGenerator(codeGenerator,
auto node = parser.ParseExpression("-+-MyExtension::MouseX(,)");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);

View File

@@ -19,16 +19,17 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto& layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
gd::ExpressionParser2 parser;
auto getCompletionsFor = [&](const gd::String& type,
const gd::String& expression,
size_t location) {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
return gd::ExpressionCompletionFinder::GetCompletionDescriptionsFor(
*node, location);
platform, project, layout1, type, *node, location);
};
const std::vector<gd::ExpressionCompletionDescription>
@@ -66,6 +67,24 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
REQUIRE(getCompletionsFor("number|string", "My", 2) ==
expectedEmptyCompletions);
}
SECTION("Object or expression completions in a variable name") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("string", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"string", "My", 0, 2)};
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My", 52) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My", 53) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[\"abc\" + My", 54) == expectedEmptyCompletions);
}
SECTION("Object or expression completions in a variable index") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("number", "My", 0, 2),
gd::ExpressionCompletionDescription::ForExpression(
"number", "My", 0, 2)};
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[12345 + My", 52) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[12345 + My", 53) == expectedCompletions);
REQUIRE(getCompletionsFor("number", "MyExtension::GetVariableAsNumber(MyVariable[12345 + My", 54) == expectedEmptyCompletions);
}
SECTION("Object when type is an object") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForObject("object", "My", 0, 2)};
@@ -143,6 +162,17 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"MyExtension::GetVariableAsNumber(myVar",
33) == expectedCompletions);
}
SECTION("Object function with a Variable as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForVariable(
"objectvar", "myVar", 35, 40, "MyObject")};
getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myVar",
35);
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myVar",
35) == expectedCompletions);
}
SECTION("Function with a Layer as argument") {
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
gd::ExpressionCompletionDescription::ForText(

View File

@@ -14,10 +14,9 @@
template <class TNode>
bool CheckNodeAtLocationIs(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
return dynamic_cast<TNode*>(
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(
@@ -26,10 +25,9 @@ bool CheckNodeAtLocationIs(gd::ExpressionParser2& parser,
template <class TNode>
bool CheckParentNodeAtLocationIs(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
return dynamic_cast<TNode*>(
gd::ExpressionNodeLocationFinder::GetParentNodeAtPosition(
@@ -37,20 +35,18 @@ bool CheckParentNodeAtLocationIs(gd::ExpressionParser2& parser,
}
bool CheckNoNodeAtLocation(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
return gd::ExpressionNodeLocationFinder::GetNodeAtPosition(
*node, searchPosition) == nullptr;
}
bool CheckNoParentNodeAtLocation(gd::ExpressionParser2& parser,
const gd::String& type,
const gd::String& expression,
size_t searchPosition) {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
return gd::ExpressionNodeLocationFinder::GetParentNodeAtPosition(
*node, searchPosition) == nullptr;
@@ -63,21 +59,21 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
auto& layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
gd::ExpressionParser2 parser;
SECTION("Empty expressions") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "string", "", 0) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "string", "", 1) ==
parser, "", 0) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "", 1) ==
true);
}
SECTION("Test 2") {
REQUIRE(CheckNoNodeAtLocation(parser, "string", " ", 0) ==
REQUIRE(CheckNoNodeAtLocation(parser, " ", 0) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "string", " ", 1) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "string", " ", 2) ==
parser, " ", 1) == true);
REQUIRE(CheckNoNodeAtLocation(parser, " ", 2) ==
true);
}
}
@@ -85,270 +81,255 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
SECTION("Valid text") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello world\"", 0) == true);
parser, "\"Hello world\"", 0) == true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello world\"", 1) == true);
parser, "\"Hello world\"", 1) == true);
}
SECTION("Test 3") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello world\"", 12) == true);
parser, "\"Hello world\"", 12) == true);
}
SECTION("Test 4") {
REQUIRE(CheckNoNodeAtLocation(parser, "string", "\"Hello world\"", 13) ==
REQUIRE(CheckNoNodeAtLocation(parser, "\"Hello world\"", 13) ==
true);
}
SECTION("Test 5") {
REQUIRE(CheckNoNodeAtLocation(parser, "string", "\"Hello world\"", 99) ==
REQUIRE(CheckNoNodeAtLocation(parser, "\"Hello world\"", 99) ==
true);
}
}
SECTION("Valid text operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" + \"World\"", 1) == true);
parser, "\"Hello \" + \"World\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "string", "\"Hello \" + \"World\"", 8) == true);
parser, "\"Hello \" + \"World\"", 8) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" + \"World\"", 15) == true);
parser, "\"Hello \" + \"World\"", 15) == true);
}
}
SECTION("Invalid texts") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "string", "\"", 0) ==
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "\"", 0) ==
true);
REQUIRE(CheckNoNodeAtLocation(parser, "string", "\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "string", "\"a", 1) ==
REQUIRE(CheckNoNodeAtLocation(parser, "\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "\"a", 1) ==
true);
}
SECTION("Invalid parenthesis") {
REQUIRE(CheckNodeAtLocationIs<gd::SubExpressionNode>(
parser, "string", "((\"hello\"", 1) == true);
parser, "((\"hello\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "((\"hello\"", 2) == true);
parser, "((\"hello\"", 2) == true);
}
SECTION("Invalid text operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" - \"World\"", 0) == true);
parser, "\"Hello \" - \"World\"", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" - \"World\"", 1) == true);
parser, "\"Hello \" - \"World\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "string", "\"Hello \" / \"World\"", 8) == true);
parser, "\"Hello \" / \"World\"", 8) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser, "string", "\"Hello \" * \"World\"", 15) == true);
parser, "\"Hello \" * \"World\"", 15) == true);
}
}
SECTION("Valid unary operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-123", 0) == true);
parser, "-123", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "+123", 0) == true);
parser, "+123", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-123", 1) == true);
parser, "-123", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-123", 2) == true);
parser, "-123", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-123", 3) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "number", "-123", 4) == true);
parser, "-123", 3) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "-123", 4) == true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-+-123", 0) == true);
parser, "-+-123", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-+-123", 1) == true);
parser, "-+-123", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::UnaryOperatorNode>(
parser, "number", "-+-123", 2) == true);
parser, "-+-123", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "-+-123", 3) == true);
parser, "-+-123", 3) == true);
}
}
SECTION("Invalid number operators") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 0) == true);
parser, "12 ! 34", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 1) == true);
parser, "12 ! 34", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 ! 34", 2) == true);
parser, "12 ! 34", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 ! 34", 3) == true);
parser, "12 ! 34", 3) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 ! 34", 4) == true);
parser, "12 ! 34", 4) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 5) == true);
parser, "12 ! 34", 5) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 ! 34", 6) == true);
parser, "12 ! 34", 6) == true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "1 / /2", 0) == true);
parser, "1 / /2", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "1 / /2", 1) == true);
parser, "1 / /2", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "1 / /2", 2) == true);
parser, "1 / /2", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "1 / /2", 3) == true);
parser, "1 / /2", 3) == true);
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "number", "1 / /2", 4) == true);
parser, "1 / /2", 4) == true);
REQUIRE(CheckNodeAtLocationIs<gd::EmptyNode>(
parser, "number", "1 / /2", 5) == true);
parser, "1 / /2", 5) == true);
}
}
SECTION("Numbers and texts mismatchs") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(parser, "number", "12+\"hello\"", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(parser, "number", "12+\"hello\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "number", "12+\"hello\"", 3) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(parser, "12+\"hello\"", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(parser, "12+\"hello\"", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(parser, "12+\"hello\"", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(parser, "12+\"hello\"", 3) == true);
}
SECTION("Numbers and texts mismatchs (parent node)") {
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 0) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 1) == true);
REQUIRE(CheckNoParentNodeAtLocation(parser, "number", "12+\"hello\"", 2) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "number", "12+\"hello\"", 3) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "12+\"hello\"", 0) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "12+\"hello\"", 1) == true);
REQUIRE(CheckNoParentNodeAtLocation(parser, "12+\"hello\"", 2) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(parser, "12+\"hello\"", 3) == true);
}
SECTION("Valid objects") {
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 0) == true);
parser, "HelloWorld1", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 1) == true);
parser, "HelloWorld1", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "HelloWorld1", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "object", "HelloWorld1", 11) == true);
parser, "HelloWorld1", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "HelloWorld1", 11) == true);
}
SECTION("Valid objects (parent node)") {
REQUIRE(CheckNoParentNodeAtLocation(
parser, "object", "HelloWorld1", 0) == true);
parser, "HelloWorld1", 0) == true);
}
SECTION("Invalid objects") {
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "a+b", 0) == true);
parser, "a+b", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "object", "a+b", 1) == true);
parser, "a+b", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "object", "a+b", 2) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "object", "a+b", 3) == true);
parser, "a+b", 2) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "a+b", 3) == true);
}
SECTION("Valid function calls") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 + MyExtension::GetNumber()", 0) ==
parser, "12 + MyExtension::GetNumber()", 0) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "12 + MyExtension::GetNumber()", 1) ==
parser, "12 + MyExtension::GetNumber()", 1) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 2) ==
parser, "12 + MyExtension::GetNumber()", 2) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 3) ==
parser, "12 + MyExtension::GetNumber()", 3) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 4) ==
parser, "12 + MyExtension::GetNumber()", 4) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "12 + MyExtension::GetNumber()", 5) ==
parser, "12 + MyExtension::GetNumber()", 5) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "12 + MyExtension::GetNumber()", 27) ==
parser, "12 + MyExtension::GetNumber()", 27) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "12 + MyExtension::GetNumber()", 28) ==
parser, "12 + MyExtension::GetNumber()", 28) ==
true);
REQUIRE(CheckNoNodeAtLocation(
parser, "number", "12 + MyExtension::GetNumber()", 29) ==
parser, "12 + MyExtension::GetNumber()", 29) ==
true);
}
SECTION("Test 2") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
33) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
34) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
35) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
36) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
37) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
38) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
39) == true);
REQUIRE(CheckNodeAtLocationIs<gd::TextNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
50) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
51) == true);
REQUIRE(CheckNoNodeAtLocation(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
52) == true);
}
SECTION("Parent node") {
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 0) ==
parser, "12 + MyExtension::GetNumber()", 0) ==
true);
REQUIRE(CheckParentNodeAtLocationIs<gd::OperatorNode>(
parser, "number", "12 + MyExtension::GetNumber()", 6) ==
parser, "12 + MyExtension::GetNumber()", 6) ==
true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
35) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
35) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::FunctionCallNode>(
parser,
"number",
"MyExtension::GetNumberWith2Params(12, \"hello world\")",
39) == true);
}
@@ -356,66 +337,67 @@ TEST_CASE("ExpressionNodeLocationFinder", "[common][events]") {
SECTION("Invalid function calls") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 0) == true);
parser, "Idontexist(12)", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 1) == true);
parser, "Idontexist(12)", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 2) == true);
parser, "Idontexist(12)", 2) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 10) == true);
parser, "Idontexist(12)", 10) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "Idontexist(12)", 11) == true);
parser, "Idontexist(12)", 11) == true);
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "Idontexist(12)", 12) == true);
parser, "Idontexist(12)", 12) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 13) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "number", "Idontexist(12)", 14) ==
parser, "Idontexist(12)", 13) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "Idontexist(12)", 14) ==
true);
}
SECTION("Invalid function calls (parent node)") {
REQUIRE(CheckNodeAtLocationIs<gd::NumberNode>(
parser, "number", "Idontexist(12)", 12) == true);
parser, "Idontexist(12)", 12) == true);
REQUIRE(CheckParentNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(12)", 12) == true);
parser, "Idontexist(12)", 12) == true);
}
SECTION("Unterminated function calls") {
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(", 0) == true);
parser, "Idontexist(", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(", 1) == true);
parser, "Idontexist(", 1) == true);
REQUIRE(CheckNodeAtLocationIs<gd::FunctionCallNode>(
parser, "number", "Idontexist(", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "number", "Idontexist(", 11) == true);
parser, "Idontexist(", 10) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "Idontexist(", 11) == true);
}
SECTION("Valid variables") {
SECTION("Test 1") {
REQUIRE(CheckNodeAtLocationIs<gd::VariableNode>(
parser, "scenevar", "myVariable", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::VariableNode>(
parser, "scenevar", "myVariable", 9) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "scenevar", "myVariable", 10) ==
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "myVariable", 0) == true);
REQUIRE(CheckNodeAtLocationIs<gd::IdentifierNode>(
parser, "myVariable", 9) == true);
REQUIRE(CheckNoNodeAtLocation(parser, "myVariable", 10) ==
true);
}
SECTION("Test 2") {
auto node = parser.ParseExpression("scenevar", "Var1.Child1");
auto node = parser.ParseExpression("Var1.Child1");
REQUIRE(node != nullptr);
auto var1Node =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 0);
REQUIRE(dynamic_cast<gd::VariableNode*>(var1Node) != nullptr);
REQUIRE(dynamic_cast<gd::VariableNode&>(*var1Node).name == "Var1");
REQUIRE(dynamic_cast<gd::IdentifierNode*>(var1Node) != nullptr);
REQUIRE(dynamic_cast<gd::IdentifierNode&>(*var1Node).identifierName == "Var1");
// It's actually the same node.
auto child1Node =
gd::ExpressionNodeLocationFinder::GetNodeAtPosition(*node, 4);
REQUIRE(dynamic_cast<gd::VariableAccessorNode*>(child1Node) != nullptr);
REQUIRE(dynamic_cast<gd::VariableAccessorNode&>(*child1Node).name ==
REQUIRE(dynamic_cast<gd::IdentifierNode*>(child1Node) != nullptr);
REQUIRE(dynamic_cast<gd::IdentifierNode&>(*child1Node).childIdentifierName ==
"Child1");
}
SECTION("Test 3") {
auto node = parser.ParseExpression(
"scenevar", "myVariable[ \"My named children\" ].grandChild");
"myVariable[ \"My named children\" ].grandChild");
REQUIRE(node != nullptr);
auto myVariableNode =

File diff suppressed because it is too large Load Diff

View File

@@ -21,13 +21,15 @@ TEST_CASE("ExpressionParser2 - Benchmarks", "[common][events]") {
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
gd::ExpressionParser2 parser;
auto parseExpression = [&parser](const gd::String &expression) {
auto parseExpressionWithType = [&parser,
auto parseExpression = [&parser, &project, &platform, &layout1](const gd::String &expression) {
auto parseExpressionWithType = [&parser, &project, &platform, &layout1,
&expression](const gd::String &type) {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, project, layout1, type);
node->Visit(validator);
};
parseExpressionWithType("number");

View File

@@ -21,7 +21,7 @@ TEST_CASE("ExpressionParser2 - Naughty strings", "[common][events]") {
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
gd::ExpressionParser2 parser;
SECTION("Check that no naughty string crash the parser") {
std::string inputFile = std::string(__FILE__) + "-blns.txt";
@@ -31,9 +31,9 @@ TEST_CASE("ExpressionParser2 - Naughty strings", "[common][events]") {
std::string line;
size_t count = 0;
while ( std::getline (myfile,line) ) {
auto node1 = parser.ParseExpression("string", line.c_str());
auto node1 = parser.ParseExpression(line.c_str());
REQUIRE(node1 != nullptr);
auto node2 = parser.ParseExpression("number", line.c_str());
auto node2 = parser.ParseExpression(line.c_str());
REQUIRE(node2 != nullptr);
count++;

View File

@@ -20,12 +20,12 @@ TEST_CASE("ExpressionParser2NodePrinter", "[common][events]") {
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
gd::ExpressionParser2 parser(platform, project, layout1);
gd::ExpressionParser2 parser;
auto testPrinter = [&parser](const gd::String &type,
const gd::String &expression,
const gd::String &expectedOutput = "") {
auto node = parser.ParseExpression(type, expression);
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
gd::ExpressionParser2NodePrinter printer;
node->Visit(printer);

View File

@@ -1,5 +1,5 @@
// @ts-check
describe.only('gdjs.AnchorRuntimeBehavior', function () {
describe('gdjs.AnchorRuntimeBehavior', function () {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
resources: { resources: [] },

View File

@@ -34,7 +34,6 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<DestroyOutsideBehavior>(),
std::shared_ptr<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.AddCondition("ExtraBorder",
_("Additional border"),
_("Compare the additional border that the object must cross "
@@ -47,8 +46,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "DestroyOutside")
.UseStandardRelationalOperatorParameters("number")
.MarkAsAdvanced()
.SetFunctionName("GetExtraBorder")
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideRuntimeBehavior.h");
.SetFunctionName("GetExtraBorder");
aut.AddAction("ExtraBorder",
_("Additional border"),
@@ -63,7 +61,5 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
.UseStandardOperatorParameters("number")
.MarkAsAdvanced()
.SetFunctionName("SetExtraBorder")
.SetGetter("GetExtraBorder")
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideRuntimeBehavior.h");
#endif
.SetGetter("GetExtraBorder");
}

View File

@@ -24,7 +24,6 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddInstructionOrExpressionGroupMetadata(_("Inventories"))
.SetIcon("CppPlatform/Extensions/Inventoryicon.png");
#if defined(GD_IDE_ONLY)
extension
.AddAction("Add",
_("Add an item"),
@@ -37,8 +36,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Add")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::Add");
extension
.AddAction("Remove",
@@ -52,8 +50,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Remove")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::Remove");
extension
.AddCondition("Count",
@@ -68,8 +65,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("InventoryTools::Count")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::Count");
extension
.AddCondition("Has",
@@ -84,8 +80,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Has")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::Has");
extension
.AddAction("SetMaximum",
@@ -103,8 +98,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.AddParameter("expression", _("Maximum count"))
.SetFunctionName("InventoryTools::SetMaximum")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::SetMaximum");
extension
.AddAction("SetUnlimited",
@@ -121,8 +115,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.AddParameter("yesorno", _("Allow an unlimited amount?"))
.SetFunctionName("InventoryTools::SetUnlimited")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::SetUnlimited");
extension
.AddCondition("IsFull",
@@ -137,8 +130,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Has")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::Has");
extension
.AddAction("Equip",
@@ -154,8 +146,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.AddParameter("yesorno", _("Equip?"))
.SetFunctionName("InventoryTools::Equip")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::Equip");
extension
.AddCondition("IsEquipped",
@@ -169,8 +160,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::IsEquipped")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::IsEquipped");
extension
.AddAction("SerializeToVariable",
@@ -185,8 +175,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("scenevar", _("Scene variable"))
.SetFunctionName("InventoryTools::SerializeToVariable")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::SerializeToVariable");
extension
.AddAction("UnserializeFromVariable",
@@ -200,8 +189,7 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("scenevar", _("Scene variable"))
.SetFunctionName("InventoryTools::UnserializeFromVariable")
.SetIncludeFile("Inventory/InventoryTools.h");
.SetFunctionName("InventoryTools::UnserializeFromVariable");
extension
.AddExpression("Count",
@@ -212,7 +200,5 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Count")
.SetIncludeFile("Inventory/InventoryTools.h");
#endif
.SetFunctionName("InventoryTools::Count");
}

View File

@@ -133,7 +133,7 @@ module.exports = {
'LastSaveError',
_('Error of last save attempt'),
_('Get the error of the last save attempt.'),
_('Error of last save attempt in leaderboard _PARAM0_'),
_('Save score'),
'JsPlatform/Extensions/leaderboard.svg'
)
.addParameter('leaderboardId', _('Leaderboard'), '', true)

View File

@@ -28,8 +28,6 @@ void DeclareLinkedObjectsExtension(gd::PlatformExtension& extension) {
extension.AddInstructionOrExpressionGroupMetadata(_("Linked objects"))
.SetIcon("CppPlatform/Extensions/LinkedObjectsicon24.png");
#if defined(GD_IDE_ONLY)
extension
.AddAction("LinkObjects",
_("Link two objects"),
@@ -44,8 +42,7 @@ void DeclareLinkedObjectsExtension(gd::PlatformExtension& extension) {
.AddParameter("objectPtr", _("Object 1"))
.AddParameter("objectPtr", _("Object 2"))
.SetFunctionName("GDpriv::LinkedObjects::LinkObjects")
.SetIncludeFile("LinkedObjects/LinkedObjectsTools.h");
.SetFunctionName("GDpriv::LinkedObjects::LinkObjects");
extension
.AddAction("RemoveLinkBetween",
@@ -60,8 +57,7 @@ void DeclareLinkedObjectsExtension(gd::PlatformExtension& extension) {
.AddParameter("objectPtr", _("Object 1"))
.AddParameter("objectPtr", _("Object 2"))
.SetFunctionName("GDpriv::LinkedObjects::RemoveLinkBetween")
.SetIncludeFile("LinkedObjects/LinkedObjectsTools.h");
.SetFunctionName("GDpriv::LinkedObjects::RemoveLinkBetween");
extension
.AddAction("RemoveAllLinksOf",
@@ -75,8 +71,7 @@ void DeclareLinkedObjectsExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("objectPtr", _("Object"))
.SetFunctionName("GDpriv::LinkedObjects::RemoveAllLinksOf")
.SetIncludeFile("LinkedObjects/LinkedObjectsTools.h");
.SetFunctionName("GDpriv::LinkedObjects::RemoveAllLinksOf");
extension
.AddCondition("PickObjectsLinkedTo",
@@ -94,8 +89,7 @@ void DeclareLinkedObjectsExtension(gd::PlatformExtension& extension) {
.AddParameter("objectPtr", _("...if they are linked to this object"))
.AddCodeOnlyParameter("eventsFunctionContext", "")
.SetFunctionName("GDpriv::LinkedObjects::PickObjectsLinkedTo")
.SetIncludeFile("LinkedObjects/LinkedObjectsTools.h");
.SetFunctionName("GDpriv::LinkedObjects::PickObjectsLinkedTo");
extension
.AddAction(
@@ -112,8 +106,5 @@ void DeclareLinkedObjectsExtension(gd::PlatformExtension& extension) {
.AddParameter("objectPtr", _("...if they are linked to this object"))
.AddCodeOnlyParameter("eventsFunctionContext", "")
.SetFunctionName("GDpriv::LinkedObjects::PickObjectsLinkedTo")
.SetIncludeFile("LinkedObjects/LinkedObjectsTools.h");
#endif
.SetFunctionName("GDpriv::LinkedObjects::PickObjectsLinkedTo");
}

View File

@@ -36,9 +36,6 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/PanelSpriteIcon.png")
.SetCategoryFullName(_("General"));
#if defined(GD_IDE_ONLY)
obj.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
obj.AddCondition("Opacity",
_("Opacity"),
_("Compare the opacity of a Panel Sprite, between 0 (fully "
@@ -94,8 +91,7 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetWidth")
.SetGetter("GetWidth")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
.SetGetter("GetWidth");
obj.AddCondition("Width",
_("Width"),
@@ -107,8 +103,7 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetWidth")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
.SetFunctionName("GetWidth");
obj.AddAction("Height",
_("Height"),
@@ -121,8 +116,7 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetHeight")
.SetGetter("GetHeight")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
.SetGetter("GetHeight");
obj.AddCondition("Height",
_("Height"),
@@ -135,8 +129,7 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("SetHeight")
.SetGetter("GetHeight")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
.SetGetter("GetHeight");
obj.AddAction("Angle",
"Angle",
@@ -150,8 +143,7 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetAngle")
.SetGetter("GetAngle")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
.SetGetter("GetAngle");
obj.AddCondition("Angle",
"Angle",
@@ -165,8 +157,7 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("SetAngle")
.SetGetter("GetAngle")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
.SetGetter("GetAngle");
obj.AddAction("Image",
_("Image name"),
@@ -178,7 +169,5 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "PanelSprite")
.AddParameter("string", _("Image name"))
.AddCodeOnlyParameter("currentScene", "0")
.SetFunctionName("ChangeAndReloadImage")
.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
#endif
.SetFunctionName("ChangeAndReloadImage");
}

View File

@@ -39,8 +39,6 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/particleSystemicon.png")
.SetCategoryFullName(_("General"));
obj.SetIncludeFile("ParticleSystem/ParticleEmitterObject.h");
// Declaration is too big to be compiled by GCC in one file, unless you have
// 4GB+ ram. :/
ExtensionSubDeclaration1(obj);

View File

@@ -45,8 +45,7 @@ void ExtensionSubDeclaration1(gd::ObjectMetadata& obj) {
.AddParameter("object", _("Object"), "ParticleEmitter")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetAngle")
.SetGetter("GetAngle")
.SetIncludeFile("ParticleSystem/ParticleEmitterObject.h");
.SetGetter("GetAngle");
obj.AddCondition("EmitterAngle",
_("Emission angle"),

View File

@@ -37,10 +37,6 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<PathfindingBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("SetDestination",
_("Move to a position"),
_("Move the object to a position"),
@@ -54,8 +50,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Destination X position"))
.AddParameter("expression", _("Destination Y position"))
.SetFunctionName("MoveTo")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("MoveTo");
aut.AddCondition("PathFound",
_("Path found"),
@@ -67,8 +62,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("PathFound")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("PathFound");
aut.AddCondition("DestinationReached",
_("Destination reached"),
@@ -80,8 +74,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("DestinationReached")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("DestinationReached");
aut.AddAction("CellWidth",
_("Width of the cells"),
@@ -95,8 +88,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetCellWidth")
.SetGetter("GetCellWidth")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetCellWidth");
aut.AddCondition("CellWidth",
_("Width of the virtual grid"),
@@ -109,8 +101,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetCellWidth")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetCellWidth");
aut.AddAction("CellHeight",
_("Height of the cells"),
@@ -124,8 +115,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetCellHeight")
.SetGetter("GetCellHeight")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetCellHeight");
aut.AddCondition("CellHeight",
_("Height of the virtual grid"),
@@ -138,8 +128,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetCellHeight")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetCellHeight");
aut.AddAction("Acceleration",
_("Acceleration"),
@@ -153,8 +142,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetAcceleration")
.SetGetter("GetAcceleration")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetAcceleration");
aut.AddCondition("Acceleration",
_("Acceleration"),
@@ -167,8 +155,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetAcceleration")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetAcceleration");
aut.AddAction("MaxSpeed",
_("Maximum speed"),
@@ -182,8 +169,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetMaxSpeed")
.SetGetter("GetMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetMaxSpeed");
aut.AddCondition("MaxSpeed",
_("Maximum speed"),
@@ -196,8 +182,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetMaxSpeed");
aut.AddAction("Speed",
_("Speed"),
@@ -211,8 +196,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetSpeed")
.SetGetter("GetSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetSpeed");
aut.AddCondition("Speed",
_("Speed on its path"),
@@ -225,8 +209,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetSpeed");
aut.AddScopedCondition("MovementAngleIsAround",
_("Angle of movement on its path"),
@@ -254,8 +237,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetAngularMaxSpeed")
.SetGetter("GetAngularMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetAngularMaxSpeed");
aut.AddCondition(
"AngularMaxSpeed",
@@ -269,8 +251,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetAngularMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetAngularMaxSpeed");
aut.AddAction(
"AngleOffset",
@@ -285,8 +266,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetAngleOffset")
.SetGetter("GetAngleOffset")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetAngleOffset");
aut.AddCondition("AngleOffset",
_("Rotation offset"),
@@ -299,8 +279,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetAngleOffset")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetAngleOffset");
aut.AddAction(
"ExtraBorder",
@@ -316,8 +295,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetExtraBorder")
.SetGetter("GetExtraBorder")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetGetter("GetExtraBorder");
aut.AddCondition("ExtraBorder",
_("Extra border"),
@@ -331,8 +309,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetExtraBorder")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetExtraBorder");
aut.AddAction(
"AllowDiagonals",
@@ -346,8 +323,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("yesorno", _("Allow?"))
.SetFunctionName("SetAllowDiagonals")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("SetAllowDiagonals");
aut.AddCondition("DiagonalsAllowed",
_("Diagonal movement"),
@@ -360,8 +336,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("DiagonalsAllowed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("DiagonalsAllowed");
aut.AddAction("RotateObject",
_("Rotate the object"),
@@ -374,8 +349,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("yesorno", _("Rotate object?"))
.SetFunctionName("SetRotateObject")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("SetRotateObject");
aut.AddCondition("ObjectRotated",
_("Object rotated"),
@@ -388,8 +362,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("IsObjectRotated")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("IsObjectRotated");
aut.AddExpression("GetNodeX",
_("Get a waypoint X position"),
@@ -399,8 +372,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Node index (start at 0!)"))
.SetFunctionName("GetNodeX")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetNodeX");
aut.AddExpression("GetNodeY",
_("Get a waypoint Y position"),
@@ -410,8 +382,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Node index (start at 0!)"))
.SetFunctionName("GetNodeY")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetNodeY");
aut.AddExpression("NextNodeIndex",
_("Index of the next waypoint"),
@@ -420,8 +391,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeIndex")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetNextNodeIndex");
aut.AddExpression("NodeCount",
_("Waypoint count"),
@@ -430,8 +400,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNodeCount")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetNodeCount");
aut.AddExpression("NextNodeX",
_("Get next waypoint X position"),
@@ -440,8 +409,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeX")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetNextNodeX");
aut.AddExpression("NextNodeY",
_("Get next waypoint Y position"),
@@ -450,8 +418,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeY")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetNextNodeY");
aut.AddExpression("LastNodeX",
_("Last waypoint X position"),
@@ -460,8 +427,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetLastNodeX")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetLastNodeX");
aut.AddExpression("LastNodeY",
_("Last waypoint Y position"),
@@ -470,8 +436,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetLastNodeY")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetLastNodeY");
aut.AddExpression("DestinationX",
_("Destination X position"),
@@ -480,8 +445,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetDestinationX")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetDestinationX");
aut.AddExpression("DestinationY",
_("Destination Y position"),
@@ -490,8 +454,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetDestinationY")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetDestinationY");
aut.AddExpression("Acceleration",
_("Acceleration"),
@@ -500,8 +463,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAcceleration")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetAcceleration");
aut.AddExpression("MaxSpeed",
_("Maximum speed"),
@@ -510,8 +472,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetMaxSpeed");
aut.AddExpression("Speed",
_("Speed"),
@@ -520,8 +481,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetSpeed");
aut.AddExpression("AngularMaxSpeed",
_("Angular maximum speed"),
@@ -530,8 +490,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAngularMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetAngularMaxSpeed");
aut.AddExpression("AngleOffset",
_("Rotation offset"),
@@ -540,8 +499,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAngleOffset")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetAngleOffset");
aut.AddExpression("ExtraBorder",
_("Extra border size"),
@@ -550,8 +508,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetExtraBorder")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetExtraBorder");
aut.AddExpression("CellWidth",
_("Width of a cell"),
@@ -560,8 +517,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetCellWidth")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetCellWidth");
aut.AddExpression("CellHeight",
_("Height of a cell"),
@@ -570,8 +526,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetCellHeight")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
.SetFunctionName("GetCellHeight");
aut.AddExpression("MovementAngle",
_("Angle of movement on its path"),
@@ -603,7 +558,6 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardParameters("number");
#endif
}
{
gd::BehaviorMetadata& aut = extension.AddBehavior(
@@ -617,10 +571,6 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<PathfindingObstacleBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile(
"PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
aut.AddAction("Cost",
_("Cost"),
_("Change the cost of going through the object."),
@@ -633,9 +583,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetCost")
.SetGetter("GetCost")
.SetIncludeFile(
"PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
.SetGetter("GetCost");
aut.AddCondition("Cost",
_("Cost"),
@@ -648,9 +596,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetCost")
.SetIncludeFile(
"PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
.SetFunctionName("GetCost");
aut.AddAction("SetImpassable",
_("Should object be impassable?"),
@@ -663,9 +609,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.AddParameter("yesorno", _("Impassable?"))
.SetFunctionName("SetImpassable")
.SetIncludeFile(
"PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
.SetFunctionName("SetImpassable");
aut.AddCondition("IsImpassable",
_("Is object impassable?"),
@@ -677,9 +621,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.SetFunctionName("IsImpassable")
.SetIncludeFile(
"PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
.SetFunctionName("IsImpassable");
aut.AddExpression("Cost",
_("Cost"),
@@ -688,10 +630,6 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AStaricon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.SetFunctionName("GetCost")
.SetIncludeFile(
"PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
#endif
.SetFunctionName("GetCost");
}
}

View File

@@ -648,6 +648,9 @@ namespace gdjs {
while (this._openNodes.length !== 0) {
//Make sure we do not search forever.
if (iterationCount++ > maxIterationCount) {
console.warn(
`No path was found after covering ${maxIterationCount} cells.`
);
return false;
}

View File

@@ -419,7 +419,7 @@ module.exports = {
sharedData
)
.setIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.addIncludeFile('Extensions/Physics2Behavior/box2d.js');
.addIncludeFile('Extensions/Physics2Behavior/box2d.js')
// Global
aut
@@ -1396,8 +1396,8 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('X component (N.m)'))
.addParameter('expression', _('Y component (N.m)'))
.addParameter('expression', _('X component (in Newton * seconds or kilogram * meter per second)'))
.addParameter('expression', _('Y component (in Newton * seconds or kilogram * meter per second)'))
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying Y position'))
.getCodeExtraInformation()
@@ -1420,7 +1420,7 @@ module.exports = {
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angle'))
.addParameter('expression', _('Length (N.m)'))
.addParameter('expression', _('Length (in Newton * seconds or kilogram * meter per second)'))
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying Y position'))
.getCodeExtraInformation()
@@ -1442,7 +1442,7 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Length (N.m)'))
.addParameter('expression', _('Length (in Newton * seconds or kilogram * meter per second)'))
.addParameter('expression', _('X position'))
.addParameter('expression', _('Y position'))
.addParameter('expression', _('Applying X position'))
@@ -1486,18 +1486,44 @@ module.exports = {
.getCodeExtraInformation()
.setFunctionName('applyAngularImpulse');
aut
aut
.addExpression(
'MassCenterX',
_('Mass center X'),
_('Mass center X'),
'Mass',
_('Mass'),
_('Return the mass of the object (in kilograms)'),
'',
'res/physics32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.getCodeExtraInformation()
.setFunctionName('getMassCenterX');
.setFunctionName('getMass');
aut
.addExpression(
'Inertia',
_('Inertia'),
_('Return the rotational inertia of the object (in kilograms * meters * meters)'),
'',
'res/physics32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.getCodeExtraInformation()
.setFunctionName('getInertia');
aut
.addExpression(
'MassCenterX',
_('Mass center X'),
_('Mass center X'),
'',
'res/physics32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.getCodeExtraInformation()
.setFunctionName('getMassCenterX');
aut
.addExpression(
@@ -3732,9 +3758,9 @@ module.exports = {
.addCondition(
'Collision',
_('Collision'),
_('Test if two objects collide.'),
_('Check if two objects collide.'),
_('_PARAM0_ is colliding with _PARAM2_'),
'',
'',
'res/physics32.png',
'res/physics32.png'
)
@@ -3746,6 +3772,42 @@ module.exports = {
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.setFunctionName('gdjs.physics2.objectsCollide');
extension
.addCondition(
'CollisionStarted',
_('Collision started'),
_('Check if two objects just started colliding during this frame.'),
_('_PARAM0_ started colliding with _PARAM2_'),
_('Collision'),
'res/physics32.png',
'res/physics32.png'
)
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('objectList', _('Object'), '', false)
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.setFunctionName('gdjs.physics2.haveObjectsStartedColliding');
extension
.addCondition(
'CollisionStopped',
_('Collision stopped'),
_('Check if two objects just stopped colliding at this frame.'),
_('_PARAM0_ stopped colliding with _PARAM2_'),
_('Collision'),
'res/physics32.png',
'res/physics32.png'
)
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('objectList', _('Object'), '', false)
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.setFunctionName('gdjs.physics2.haveObjectsStoppedColliding');
return extension;
},

View File

@@ -20,7 +20,13 @@ namespace gdjs {
// Start with 1 so the user is safe from default variables value (0)
joints: any = {};
// List of physics behavior in the runtimeScene. It should be updated
// when a new physics object is created (constructor), on destruction (onDestroy),
// on behavior activation (onActivate) and on behavior deactivation (onDeActivate).
_registeredBehaviors: Set<Physics2RuntimeBehavior>;
constructor(runtimeScene, sharedData) {
this._registeredBehaviors = new Set();
this.gravityX = sharedData.gravityX;
this.gravityY = sharedData.gravityY;
this.scaleX = sharedData.scaleX === 0 ? 100 : sharedData.scaleX;
@@ -48,13 +54,16 @@ namespace gdjs {
// Get associated behaviors
const behaviorA = contact.GetFixtureA().GetBody()
.gdjsAssociatedBehavior;
.gdjsAssociatedBehavior as Physics2RuntimeBehavior | null;
const behaviorB = contact.GetFixtureB().GetBody()
.gdjsAssociatedBehavior;
.gdjsAssociatedBehavior as Physics2RuntimeBehavior | null;
// Let each behavior know about the contact against the other
behaviorA.currentContacts.push(behaviorB);
behaviorB.currentContacts.push(behaviorA);
if (!behaviorA || !behaviorB) {
return;
}
behaviorA.onContactBegin(behaviorB);
behaviorB.onContactBegin(behaviorA);
};
this.contactListener.EndContact = function (contactPtr) {
// Get the contact
@@ -70,19 +79,16 @@ namespace gdjs {
// Get associated behaviors
const behaviorA = contact.GetFixtureA().GetBody()
.gdjsAssociatedBehavior;
.gdjsAssociatedBehavior as Physics2RuntimeBehavior | null;
const behaviorB = contact.GetFixtureB().GetBody()
.gdjsAssociatedBehavior;
.gdjsAssociatedBehavior as Physics2RuntimeBehavior | null;
// Remove each other contact
let i = behaviorA.currentContacts.indexOf(behaviorB);
if (i !== -1) {
behaviorA.currentContacts.splice(i, 1);
}
i = behaviorB.currentContacts.indexOf(behaviorA);
if (i !== -1) {
behaviorB.currentContacts.splice(i, 1);
if (!behaviorA || !behaviorB) {
return;
}
behaviorA.onContactEnd(behaviorB);
behaviorB.onContactEnd(behaviorA);
};
this.contactListener.PreSolve = function () {};
this.contactListener.PostSolve = function () {};
@@ -104,6 +110,40 @@ namespace gdjs {
return runtimeScene.physics2SharedData;
}
/**
* Add a physics object to the list of existing object.
*/
addToBehaviorsList(physicsBehavior: gdjs.Physics2RuntimeBehavior) {
this._registeredBehaviors.add(physicsBehavior);
}
/**
* Remove a physics object to the list of existing object.
*/
removeFromBehaviorsList(physicsBehavior: gdjs.Physics2RuntimeBehavior) {
this._registeredBehaviors.delete(physicsBehavior);
}
/**
* Reset all contactsStartedThisFrame and contactsEndedThisFrame of all
* registered physics behavior.
*/
resetStartedAndEndedCollisions() {
for (const physicsBehavior of this._registeredBehaviors) {
physicsBehavior.contactsStartedThisFrame.length = 0;
physicsBehavior.contactsEndedThisFrame.length = 0;
}
}
/**
* Update all registered body.
*/
updateBodiesFromObjects() {
for (const physicsBehavior of this._registeredBehaviors) {
physicsBehavior.updateBodyFromObject();
}
}
step(deltaTime) {
this.frameTime += deltaTime;
if (this.frameTime >= this.timeStep) {
@@ -241,11 +281,40 @@ namespace gdjs {
layers: any;
masks: any;
shapeScale: number = 1;
currentContacts: any;
/**
* Array containing the beginning of contacts reported by onContactBegin. Each contact
* should be unique to avoid recording glitches where the object loses and regain
* contact between two frames. The array is updated each time the method
* onContactBegin is called by the listener, which is only called when stepping
* the world i.e. in the first preEvent called by a physics behavior. This array is
* cleared just before stepping the world.
*/
contactsStartedThisFrame: Array<Physics2RuntimeBehavior>;
/**
* Array containing the end of contacts reported by onContactEnd. The array is updated
* each time the method onContactEnd is called by the listener, which can be called at
* any time. This array is cleared just before stepping the world.
*/
contactsEndedThisFrame: Array<Physics2RuntimeBehavior>;
/**
* Array containing the exact current contacts with the objects. It is updated
* each time the methods onContactBegin and onContactEnd are called by the contact
* listener.
*/
currentContacts: Array<Physics2RuntimeBehavior>;
destroyedDuringFrameLogic: boolean;
_body: any = null;
_sharedData: any;
_tempb2Vec2: any;
/**
* sharedData is a reference to the shared data of the scene, that registers
* every physics behavior that is created so that collisions can be cleared
* before stepping the world.
*/
_sharedData: Physics2SharedData;
// Avoid creating new vectors all the time
_tempb2Vec2Sec: any;
@@ -281,14 +350,18 @@ namespace gdjs {
this.gravityScale = behaviorData.gravityScale;
this.layers = behaviorData.layers;
this.masks = behaviorData.masks;
this.currentContacts = this.currentContacts || [];
this.contactsStartedThisFrame = [];
this.contactsEndedThisFrame = [];
this.currentContacts = [];
this.currentContacts.length = 0;
this.destroyedDuringFrameLogic = false;
this._sharedData = Physics2SharedData.getSharedData(
runtimeScene,
behaviorData.name
);
this._tempb2Vec2 = new Box2D.b2Vec2();
this._tempb2Vec2Sec = new Box2D.b2Vec2();
this._sharedData.addToBehaviorsList(this);
}
// Stores a Box2D pointer of created vertices
@@ -373,6 +446,7 @@ namespace gdjs {
}
onDeActivate() {
this._sharedData.removeFromBehaviorsList(this);
if (this._body !== null) {
// When a body is deleted, Box2D removes automatically its joints, leaving an invalid pointer in our joints list
this._sharedData.clearBodyJoints(this._body);
@@ -387,9 +461,22 @@ namespace gdjs {
this._sharedData.world.DestroyBody(this._body);
this._body = null;
}
this.contactsEndedThisFrame.length = 0;
this.contactsStartedThisFrame.length = 0;
this.currentContacts.length = 0;
}
onActivate() {
this._sharedData.addToBehaviorsList(this);
this.contactsEndedThisFrame.length = 0;
this.contactsStartedThisFrame.length = 0;
this.currentContacts.length = 0;
this.updateBodyFromObject();
}
onDestroy() {
this.destroyedDuringFrameLogic = true;
this.onDeActivate();
}
@@ -637,8 +724,7 @@ namespace gdjs {
recreateShape() {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Destroy the old shape
@@ -669,7 +755,8 @@ namespace gdjs {
return this._body;
}
createBody() {
createBody(): boolean {
if (!this.activated() || this.destroyedDuringFrameLogic) return false;
// Generate the body definition
const bodyDef = new Box2D.b2BodyDef();
@@ -707,57 +794,60 @@ namespace gdjs {
// Update cached size
this._objectOldWidth = this.owner.getWidth();
this._objectOldHeight = this.owner.getHeight();
return true;
}
doStepPreEvents(runtimeScene) {
// Create a body if there is not one
if (this._body === null) {
this.createBody();
}
// Step the world if not done this frame yet
if (!this._sharedData.stepped) {
// Reset started and ended contacts array for all physics instances.
this._sharedData.resetStartedAndEndedCollisions();
this._sharedData.updateBodiesFromObjects();
this._sharedData.step(
runtimeScene.getTimeManager().getElapsedTime() / 1000.0
);
}
// Copy transform from body to the GD object
this.owner.setX(
this._body.GetPosition().get_x() * this._sharedData.scaleX -
this.owner.getWidth() / 2 +
this.owner.getX() -
this.owner.getDrawableX()
);
this.owner.setY(
this._body.GetPosition().get_y() * this._sharedData.scaleY -
this.owner.getHeight() / 2 +
this.owner.getY() -
this.owner.getDrawableY()
);
this.owner.setAngle(gdjs.toDegrees(this._body.GetAngle()));
// Copy transform from body to the GD object.
// It's possible the behavior was either deactivated or the object deleted
// just before this doStepPreEvents (for example, another behavior deleted
// the object during its own doStepPreEvents). If the body is null, we just
// don't do anything (but still run the physics simulation - this is independent).
if (this._body !== null) {
this.owner.setX(
this._body.GetPosition().get_x() * this._sharedData.scaleX -
this.owner.getWidth() / 2 +
this.owner.getX() -
this.owner.getDrawableX()
);
this.owner.setY(
this._body.GetPosition().get_y() * this._sharedData.scaleY -
this.owner.getHeight() / 2 +
this.owner.getY() -
this.owner.getDrawableY()
);
this.owner.setAngle(gdjs.toDegrees(this._body.GetAngle()));
}
// Update cached transform
// Update cached transform.
this._objectOldX = this.owner.getX();
this._objectOldY = this.owner.getY();
this._objectOldAngle = this.owner.getAngle();
}
doStepPostEvents(runtimeScene) {
this._updateBodyFromObject();
// Reset world step to update next frame
this._sharedData.stepped = false;
}
onObjectHotReloaded() {
this._updateBodyFromObject();
this.updateBodyFromObject();
}
_updateBodyFromObject() {
updateBodyFromObject() {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// The object size has changed, recreate the shape.
@@ -854,8 +944,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update body type
@@ -878,8 +967,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update body type
@@ -902,8 +990,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update body type
@@ -926,8 +1013,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update body bullet flag
@@ -941,8 +1027,7 @@ namespace gdjs {
setFixedRotation(enable): void {
this.fixedRotation = enable;
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
this._body.SetFixedRotation(this.fixedRotation);
}
@@ -954,8 +1039,7 @@ namespace gdjs {
setSleepingAllowed(enable): void {
this.canSleep = enable;
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
this._body.SetSleepingAllowed(this.canSleep);
}
@@ -963,7 +1047,7 @@ namespace gdjs {
isSleeping(): boolean {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return true;
}
// Get the body sleeping state
@@ -990,8 +1074,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body density
@@ -1019,8 +1102,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body friction
@@ -1054,8 +1136,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body restitution
@@ -1084,8 +1165,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body linear damping
@@ -1107,8 +1187,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body angular damping
@@ -1130,8 +1209,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body gravity scale
@@ -1167,8 +1245,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body layers
@@ -1206,8 +1283,7 @@ namespace gdjs {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return;
if (!this.createBody()) return;
}
// Update the body masks
@@ -1219,8 +1295,7 @@ namespace gdjs {
getLinearVelocityX(): float {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return 0;
if (!this.createBody()) return 0;
}
// Get the linear velocity on X
@@ -1230,7 +1305,7 @@ namespace gdjs {
setLinearVelocityX(linearVelocityX): void {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Set the linear velocity on X
@@ -1245,8 +1320,7 @@ namespace gdjs {
getLinearVelocityY(): float {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return 0;
if (!this.createBody()) return 0;
}
// Get the linear velocity on Y
@@ -1256,7 +1330,7 @@ namespace gdjs {
setLinearVelocityY(linearVelocityY): void {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Set the linear velocity on Y
@@ -1268,11 +1342,10 @@ namespace gdjs {
);
}
getLinearVelocityLength() {
getLinearVelocityLength(): float {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return 0;
if (!this.createBody()) return 0;
}
// Get the linear velocity length
@@ -1282,10 +1355,10 @@ namespace gdjs {
).Length();
}
getAngularVelocity() {
getAngularVelocity(): float {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return 0;
}
// Get the angular velocity
@@ -1295,7 +1368,7 @@ namespace gdjs {
setAngularVelocity(angularVelocity): void {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Set the angular velocity
@@ -1305,7 +1378,7 @@ namespace gdjs {
applyForce(forceX, forceY, positionX, positionY) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1324,7 +1397,7 @@ namespace gdjs {
applyPolarForce(angle, length, positionX, positionY) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1344,7 +1417,7 @@ namespace gdjs {
applyForceTowardPosition(length, towardX, towardY, positionX, positionY) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1367,7 +1440,7 @@ namespace gdjs {
applyImpulse(impulseX, impulseY, positionX, positionY) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1386,7 +1459,7 @@ namespace gdjs {
applyPolarImpulse(angle, length, positionX, positionY) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1406,7 +1479,7 @@ namespace gdjs {
applyImpulseTowardPosition(length, towardX, towardY, positionX, positionY) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1429,7 +1502,7 @@ namespace gdjs {
applyTorque(torque) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1442,7 +1515,7 @@ namespace gdjs {
applyAngularImpulse(angularImpulse) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Wake up the object
@@ -1452,10 +1525,34 @@ namespace gdjs {
this._body.ApplyAngularImpulse(angularImpulse);
}
getMass(): float {
// If there is no body, set a new one
if (this._body === null) {
if (!this.createBody()) return 0;
}
// Wake up the object
this._body.SetAwake(true);
return this._body.GetMass();
}
getInertia(): float {
// If there is no body, set a new one
if (this._body === null) {
if (!this.createBody()) return 0;
}
// Wake up the object
this._body.SetAwake(true);
return this._body.GetInertia();
}
getMassCenterX(): float {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return 0;
}
// Get the mass center on X
@@ -1465,7 +1562,7 @@ namespace gdjs {
getMassCenterY(): float {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return 0;
}
// Get the mass center on Y
@@ -1476,8 +1573,7 @@ namespace gdjs {
isJointFirstObject(jointId): boolean {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return false;
if (!this.createBody()) return false;
}
// Get the joint
@@ -1495,8 +1591,7 @@ namespace gdjs {
isJointSecondObject(jointId): boolean {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
return false;
if (!this.createBody()) return false;
}
// Get the joint
@@ -1609,7 +1704,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -1780,7 +1875,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Set joint settings
@@ -1849,7 +1944,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -2140,7 +2235,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -2465,7 +2560,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -2639,7 +2734,7 @@ namespace gdjs {
addGearJoint(jointId1, jointId2, ratio, collideConnected, variable) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Get the first joint
@@ -2760,7 +2855,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// Set joint settings
@@ -2935,7 +3030,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -3211,7 +3306,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -3345,7 +3440,7 @@ namespace gdjs {
addRopeJoint(x1, y1, other, x2, y2, maxLength, collideConnected, variable) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -3452,7 +3547,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -3580,7 +3675,7 @@ namespace gdjs {
) {
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
if (!this.createBody()) return;
}
// If there is no second object or it doesn't share the behavior, return
@@ -3797,8 +3892,35 @@ namespace gdjs {
joint.GetBodyB().SetAwake(true);
}
// Collision
static collisionTest(
onContactBegin(otherBehavior: Physics2RuntimeBehavior) {
this.currentContacts.push(otherBehavior);
// There might be contacts that end during the frame and
// start again right away. It is considered a glitch
// and should not be detected.
let i = this.contactsEndedThisFrame.indexOf(otherBehavior);
if (i !== -1) {
this.contactsEndedThisFrame.splice(i, 1);
} else {
this.contactsStartedThisFrame.push(otherBehavior);
}
}
onContactEnd(otherBehavior: Physics2RuntimeBehavior) {
this.contactsEndedThisFrame.push(otherBehavior);
const index = this.currentContacts.indexOf(otherBehavior);
if (index !== -1) {
this.currentContacts.splice(index, 1);
}
}
/**
* @deprecated Prefer using `Physics2RuntimeBehavior.areObjectsColliding`.
*/
static collisionTest = Physics2RuntimeBehavior.areObjectsColliding;
static areObjectsColliding(
object1: gdjs.RuntimeObject,
object2: gdjs.RuntimeObject,
behaviorName: string
@@ -3806,18 +3928,60 @@ namespace gdjs {
// Test if the second object is in the list of contacts of the first one
const behavior1 = object1.getBehavior(
behaviorName
) as Physics2RuntimeBehavior;
if (!!behavior1) {
for (let i = 0, len = behavior1.currentContacts.length; i < len; ++i) {
if (behavior1.currentContacts[i].owner === object2) {
return true;
}
}
) as Physics2RuntimeBehavior | null;
if (!behavior1) return false;
if (
behavior1.currentContacts.some((behavior) => behavior.owner === object2)
) {
return true;
}
// If a contact has started at this frame and ended right away, it
// won't appear in current contacts but the condition should return
// true anyway.
if (
behavior1.contactsStartedThisFrame.some(
(behavior) => behavior.owner === object2
)
) {
return true;
}
// No contact found
return false;
}
static hasCollisionStartedBetween(
object1: gdjs.RuntimeObject,
object2: gdjs.RuntimeObject,
behaviorName: string
): boolean {
// Test if the second object is in the list of contacts of the first one
const behavior1 = object1.getBehavior(
behaviorName
) as Physics2RuntimeBehavior | null;
if (!behavior1) return false;
return behavior1.contactsStartedThisFrame.some(
(behavior) => behavior.owner === object2
);
}
static hasCollisionStoppedBetween(
object1: gdjs.RuntimeObject,
object2: gdjs.RuntimeObject,
behaviorName: string
): boolean {
// Test if the second object is in the list of contacts of the first one
const behavior1 = object1.getBehavior(
behaviorName
) as Physics2RuntimeBehavior | null;
if (!behavior1) return false;
return behavior1.contactsEndedThisFrame.some(
(behavior) => behavior.owner === object2
);
}
}
gdjs.registerBehavior(
'Physics2::Physics2Behavior',

View File

@@ -7,7 +7,37 @@ namespace gdjs {
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics2RuntimeBehavior.collisionTest,
gdjs.Physics2RuntimeBehavior.areObjectsColliding,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
export const haveObjectsStartedColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics2RuntimeBehavior.hasCollisionStartedBetween,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
export const haveObjectsStoppedColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics2RuntimeBehavior.hasCollisionStoppedBetween,
objectsLists1,
objectsLists2,
inverted,

View File

@@ -0,0 +1,801 @@
describe('Physics2RuntimeBehavior', () => {
class FakeAutoRemoveBehavior extends gdjs.RuntimeBehavior {
shouldDeleteInPreEvent = false;
constructor(runtimeScene, behaviorData, owner) {
super(runtimeScene, behaviorData, owner);
}
setShouldAutoRemoveInPreEvent(shouldDeleteInPreEvent) {
this.shouldDeleteInPreEvent = shouldDeleteInPreEvent;
}
doStepPreEvents(runtimeScene) {
if (this.shouldDeleteInPreEvent) {
this.owner.deleteFromScene(runtimeScene);
}
}
}
gdjs.registerBehavior(
'Physics2::FakeAutoRemoveBehavior',
FakeAutoRemoveBehavior
);
function assertCollision(object1, object2, options) {
expect(
gdjs.Physics2RuntimeBehavior.hasCollisionStartedBetween(
object1,
object2,
'Physics2'
)
).to.be(options.started);
expect(
gdjs.Physics2RuntimeBehavior.areObjectsColliding(
object1,
object2,
'Physics2'
)
).to.be(options.collision);
expect(
gdjs.Physics2RuntimeBehavior.hasCollisionStoppedBetween(
object1,
object2,
'Physics2'
)
).to.be(options.stopped);
}
function createGameWithSceneWithPhysics2SharedData() {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
resources: { resources: [] },
properties: { windowWidth: 1000, windowHeight: 1000 },
});
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [
{
name: '',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 127,
ambientLightColorB: 127,
ambientLightColorG: 127,
isLightingLayer: false,
followBaseLayerCamera: false,
},
],
variables: [],
r: 0,
v: 0,
b: 0,
mangledName: 'Scene1',
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
behaviorsSharedData: [],
objects: [],
instances: [],
});
runtimeScene.setInitialSharedDataForBehavior('Physics2', {
gravityX: 0,
gravityY: 0,
scaleX: 1,
scaleY: 1,
});
return [runtimeGame, runtimeScene];
}
function createObjectWithPhysicsBehavior(runtimeScene, behaviorProperties) {
const object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
{
name: 'Physics2',
type: 'Physics2::Physics2Behavior',
bodyType: 'Dynamic',
bullet: false,
fixedRotation: true,
canSleep: false,
shape: 'Box',
shapeDimensionA: 0,
shapeDimensionB: 0,
shapeOffsetX: 0,
shapeOffsetY: 0,
polygonOrigin: 'Center',
vertices: [],
density: 1.0,
friction: 0.01,
restitution: 1,
linearDamping: 0,
angularDamping: 0.1,
gravityScale: 1,
layers: 1,
masks: 1,
...behaviorProperties,
},
],
variables: [],
effects: [],
});
object.setCustomWidthAndHeight(10, 10);
runtimeScene.addObject(object);
/** @type {{behavior: gdjs.Physics2RuntimeBehavior, object: gdjs.RuntimeObject}} */
return { object, behavior: object.getBehavior('Physics2') };
}
describe('Behavior activation and reactivation', () => {
let runtimeGame;
let runtimeScene;
beforeEach(() => {
[runtimeGame, runtimeScene] = createGameWithSceneWithPhysics2SharedData();
});
it('should not leave a living body after removing an object', () => {
const { object, behavior } = createObjectWithPhysicsBehavior(
runtimeScene
);
// First render to have the behavior set up
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.getBody()).not.to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(1);
expect(behavior._sharedData._registeredBehaviors.has(behavior)).to.be(
true
);
// Delete object from scene
object.deleteFromScene(runtimeScene);
expect(behavior.destroyedDuringFrameLogic).to.be(true);
expect(behavior.getBody()).to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(0);
expect(behavior._sharedData._registeredBehaviors.has(behavior)).to.be(
false
);
// Call a few methods on the behavior
behavior.setLinearDamping(2);
behavior.setGravityScale(2);
// Body should still not exist
expect(behavior.getBody()).to.be(null);
});
it("doesn't raise errors if an object with a deactivated physics2 behavior is removed", () => {
const { object, behavior } = createObjectWithPhysicsBehavior(
runtimeScene
);
// First render to have the behavior set up
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.getBody()).not.to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(1);
expect(behavior._sharedData._registeredBehaviors.has(behavior)).to.be(
true
);
object.activateBehavior('Physics2', false);
expect(behavior.getBody()).to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(0);
expect(behavior._sharedData._registeredBehaviors.has(behavior)).to.be(
false
);
object.deleteFromScene(runtimeScene);
expect(behavior.destroyedDuringFrameLogic).to.be(true);
expect(behavior.getBody()).to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(0);
});
it("should not recreate object's body when setting or getting behavior properties", () => {
const { object, behavior } = createObjectWithPhysicsBehavior(
runtimeScene
);
// First render to have the behavior set up
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.getBody()).not.to.be(null);
// Deactivate behavior
object.activateBehavior('Physics2', false);
expect(behavior.getBody()).to.be(null);
// Call bunch of methods that should have no impact on the object's body
behavior.setDensity(123);
behavior.setRestitution(0.5);
behavior.getLinearVelocityLength();
behavior.applyImpulse(10, -20, 0, 0);
behavior.getMassCenterX();
// Object's body should still not exist
expect(behavior.getBody()).to.be(null);
// Reactivate behavior
object.activateBehavior('Physics2', true);
expect(behavior.getBody()).not.to.be(null);
// Behavior should have recorded what was called with its setters while it was de-activated.
expect(behavior.getDensity()).to.be(123);
expect(behavior.getRestitution()).to.be(0.5);
});
it('should clear contacts when deactivating the physics2 behavior', () => {
const fps = 60;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
// Create objects not in contact
const {
object: object1,
behavior: object1Behavior,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Dynamic',
});
object1.setPosition(100, 0);
const {
object: object2,
behavior: object2Behavior,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
restitution: 0,
});
object1.setPosition(0, 0);
expect(object1Behavior.getBody()).not.to.be(null);
expect(object2Behavior.getBody()).not.to.be(null);
expect(object1Behavior._sharedData._registeredBehaviors.size).to.be(2);
expect(
object1Behavior._sharedData._registeredBehaviors.has(object1Behavior)
).to.be(true);
expect(
object1Behavior._sharedData._registeredBehaviors.has(object2Behavior)
).to.be(true);
// Put objects in contact and assert collision started during the frame
runtimeScene.setEventsFunction(() => {
object1.setPosition(10, 0);
object2.setPosition(20, 0);
});
runtimeScene.renderAndStep(1000 / fps);
// After post event, collision should be present
assertCollision(object1, object2, {
started: true,
collision: true,
stopped: false,
});
// Reset scene events
runtimeScene.setEventsFunction(() => {});
// Deactivate physics behavior and test that collisions are cleared.
object1.activateBehavior('Physics2', false);
assertCollision(object1, object2, {
started: false,
collision: false,
// It should be false because the condition does not have sense anymore
// since the behavior is deactivated.
stopped: false,
});
// Objects should have 0 contacts in memory.
expect(object1Behavior.currentContacts.length).to.be(0);
expect(object1Behavior.contactsEndedThisFrame.length).to.be(0);
expect(object1Behavior.contactsStartedThisFrame.length).to.be(0);
expect(object1Behavior._sharedData._registeredBehaviors.size).to.be(1);
expect(
object1Behavior._sharedData._registeredBehaviors.has(object1Behavior)
).to.be(false);
expect(
object1Behavior._sharedData._registeredBehaviors.has(object2Behavior)
).to.be(true);
runtimeScene.renderAndStep(1000 / fps);
// Reactivate physics behavior and test contact
// is not immediately back on but after the first render.
object1.activateBehavior('Physics2', true);
expect(object1Behavior.currentContacts.length).to.be(0);
expect(object1Behavior.contactsEndedThisFrame.length).to.be(0);
expect(object1Behavior.contactsStartedThisFrame.length).to.be(0);
expect(object1Behavior._sharedData._registeredBehaviors.size).to.be(2);
expect(
object1Behavior._sharedData._registeredBehaviors.has(object1Behavior)
).to.be(true);
expect(
object1Behavior._sharedData._registeredBehaviors.has(object2Behavior)
).to.be(true);
runtimeScene.renderAndStep(1000 / fps);
assertCollision(object1, object2, {
started: true,
collision: true,
stopped: false,
});
});
it('should not raise an error if the object is deleted before its physics2 pre-event is run', () => {
const object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [
// Make FakeAutoRemover run before the Physics2 behavior.
{
name: 'FakeAutoRemover',
type: 'Physics2::FakeAutoRemoveBehavior',
},
{
name: 'Physics2',
type: 'Physics2::Physics2Behavior',
bodyType: 'Dynamic',
bullet: false,
fixedRotation: true,
canSleep: false,
shape: 'Box',
shapeDimensionA: 0,
shapeDimensionB: 0,
shapeOffsetX: 0,
shapeOffsetY: 0,
polygonOrigin: 'Center',
vertices: [],
density: 1.0,
friction: 0.01,
restitution: 1,
linearDamping: 0,
angularDamping: 0.1,
gravityScale: 1,
layers: 1,
masks: 1,
},
],
variables: [],
effects: [],
});
object.setCustomWidthAndHeight(10, 10);
runtimeScene.addObject(object);
/** @type {FakeAutoRemoveBehavior} */
const fakeAutoRemoverBehavior = object.getBehavior('FakeAutoRemover');
const behavior = object.getBehavior('Physics2');
// First render to have the behavior set up
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.getBody()).not.to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(1);
expect(behavior._sharedData._registeredBehaviors.has(behavior)).to.be(
true
);
fakeAutoRemoverBehavior.setShouldAutoRemoveInPreEvent(true);
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.destroyedDuringFrameLogic).to.be(true);
expect(behavior.getBody()).to.be(null);
expect(behavior._sharedData._registeredBehaviors.size).to.be(0);
});
});
describe('Contacts computation', () => {
let runtimeGame;
let runtimeScene;
beforeEach(() => {
[runtimeGame, runtimeScene] = createGameWithSceneWithPhysics2SharedData();
});
it('should detect a collision even if the contact stated and ended during the same frame', () => {
// Use a low fps to reproduce more easily.
const fps = 2;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
const {
object: movingObject,
behavior: movingObjectBehavior,
} = createObjectWithPhysicsBehavior(runtimeScene);
const {
object: staticObject,
behavior: staticObjectBehavior,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
});
staticObject.setPosition(0, 25);
movingObject.setPosition(0, 0);
movingObjectBehavior.setLinearVelocityY(40000);
let hasBounced = false;
let stepIndex = 0;
runtimeScene.setEventsFunction(() => {
if (movingObjectBehavior.getLinearVelocityY() > 0) {
// If the moving object has a positive velocity, it hasn't bounced
// on the static object
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: false,
});
} else {
hasBounced = true;
expect(movingObject.getY() < staticObject.getY()).to.be(true);
assertCollision(movingObject, staticObject, {
started: true,
collision: true,
stopped: true,
});
}
});
while (stepIndex < 10 && !hasBounced) {
runtimeScene.renderAndStep(1000 / fps);
stepIndex++;
}
runtimeScene.setEventsFunction(() => {});
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: false,
});
if (!hasBounced) {
throw new Error('Contact did not happen, nothing was tested.');
}
});
it("should detect a collision when the contact doesn't end the same frame it started", () => {
// Use a high fps to reproduce more easily.
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
const {
behavior: movingObjectBehavior,
object: movingObject,
} = createObjectWithPhysicsBehavior(runtimeScene);
const {
behavior: staticObjectBehavior,
object: staticObject,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
});
staticObject.setPosition(0, 25);
movingObject.setPosition(0, 0);
movingObjectBehavior.setLinearVelocityY(40000);
let hasBegunBouncing = false;
let stepIndex = 0;
runtimeScene.setEventsFunction(() => {
if (movingObjectBehavior.getLinearVelocityY() > 0) {
// If the moving object has a positive velocity, it hasn't bounced
// on the static object
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: false,
});
} else {
hasBegunBouncing = true;
// At first frame, collision should have only started
expect(movingObject.getY() < staticObject.getY()).to.be(true);
assertCollision(movingObject, staticObject, {
started: true,
collision: true,
stopped: false,
});
}
});
while (stepIndex < 10 && !hasBegunBouncing) {
runtimeScene.renderAndStep(1000 / fps);
stepIndex++;
}
if (!hasBegunBouncing) {
throw new Error(
'Start of contact was not detected, nothing was tested.'
);
}
// At next frame, end of collision should be detected
let hasFinishedBouncing = false;
runtimeScene.setEventsFunction(() => {
hasFinishedBouncing = true;
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: true,
});
});
runtimeScene.renderAndStep(1000 / fps);
if (!hasFinishedBouncing) {
throw new Error('End of contact was not detected, nothing was tested.');
}
});
it('should not detect a new contact while already in contact with that same object (the contact jittered).', () => {
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
const {
behavior: movingObjectBehavior,
object: movingObject,
} = createObjectWithPhysicsBehavior(runtimeScene);
const {
behavior: staticObjectBehavior,
object: staticObject,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
restitution: 0,
});
staticObject.setPosition(0, 9);
movingObject.setPosition(0, 0);
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: true,
collision: true,
stopped: false,
});
runtimeScene.setEventsFunction(() => {
// Manually call onContactEnd and onContactBegin methods to simulate
// a loss of contact followed by a contact beginning during the preEvent.
movingObject
.getBehavior('Physics2')
.onContactEnd(staticObject.getBehavior('Physics2'));
movingObject
.getBehavior('Physics2')
.onContactBegin(staticObject.getBehavior('Physics2'));
assertCollision(movingObject, staticObject, {
started: false,
collision: true,
stopped: false,
});
});
runtimeScene.renderAndStep(1000 / fps);
});
it('should not detect a new contact if the contact ended and jittered.', () => {
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
const {
behavior: movingObjectBehavior,
object: movingObject,
} = createObjectWithPhysicsBehavior(runtimeScene);
const {
behavior: staticObjectBehavior,
object: staticObject,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
restitution: 0,
});
staticObject.setPosition(0, 4);
movingObject.setPosition(0, 0);
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: true,
collision: true,
stopped: false,
});
runtimeScene.setEventsFunction(() => {
// Manually call onContactEnd and onContactBegin methods to simulate
// a loss of contact followed by a contact beginning and another loss
// of contact during the event.
movingObject
.getBehavior('Physics2')
.onContactEnd(staticObject.getBehavior('Physics2'));
movingObject
.getBehavior('Physics2')
.onContactBegin(staticObject.getBehavior('Physics2'));
movingObject
.getBehavior('Physics2')
.onContactEnd(staticObject.getBehavior('Physics2'));
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: true,
});
});
runtimeScene.renderAndStep(1000 / fps);
});
it('it should end collision on resize (body updated in pre-event).', () => {
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
const {
behavior: movingObjectBehavior,
object: movingObject,
} = createObjectWithPhysicsBehavior(runtimeScene);
const {
behavior: staticObjectBehavior,
object: staticObject,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
restitution: 0,
});
staticObject.setPosition(0, 9);
movingObject.setPosition(0, 0);
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: true,
collision: true,
stopped: false,
});
// Resize.
runtimeScene.setEventsFunction(() => {
movingObject.setCustomWidthAndHeight(5, 5);
});
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: false,
collision: true,
stopped: false,
});
runtimeScene.setEventsFunction(() => {});
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: true,
});
});
it('it should end collision on object destruction (loss of contact begins during event).', () => {
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / fps) * 1000;
};
const {
behavior: movingObjectBehavior,
object: movingObject,
} = createObjectWithPhysicsBehavior(runtimeScene);
const {
behavior: staticObjectBehavior,
object: staticObject,
} = createObjectWithPhysicsBehavior(runtimeScene, {
bodyType: 'Static',
restitution: 0,
});
staticObject.setPosition(0, 9);
movingObject.setPosition(0, 0);
runtimeScene.renderAndStep(1000 / fps);
assertCollision(movingObject, staticObject, {
started: true,
collision: true,
stopped: false,
});
// Destroy (postEvent operation).
runtimeScene.setEventsFunction(() => {
movingObject.deleteFromScene(runtimeScene);
});
runtimeScene.renderAndStep(1000 / fps);
// Collision should be reset on destroyed object and
// added to contactsEndedThisFrame array of the other object.
assertCollision(movingObject, staticObject, {
started: false,
collision: false,
stopped: false,
});
assertCollision(staticObject, movingObject, {
started: false,
collision: false,
stopped: true,
});
});
});
describe('onContactBegin', () => {
let runtimeGame;
let runtimeScene;
beforeEach(() => {
[runtimeGame, runtimeScene] = createGameWithSceneWithPhysics2SharedData();
});
it('should add behavior to list of started contacts', () => {
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
const { behavior } = createObjectWithPhysicsBehavior(runtimeScene);
const { behavior: otherBehavior } = createObjectWithPhysicsBehavior(
runtimeScene
);
behavior.onContactBegin(otherBehavior);
expect(behavior.contactsStartedThisFrame.length).to.be(1);
expect(behavior.contactsStartedThisFrame[0]).to.be(otherBehavior);
});
it('should add behavior to list of started contacts and ended contacts', () => {
// From the user point of view the objects are colliding but it could be
// quick enough for it to happen between 2 game frames (the physics model
// uses modelling sub-steps). So contact beginning and end should be detected.
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
const { behavior } = createObjectWithPhysicsBehavior(runtimeScene);
const { behavior: otherBehavior } = createObjectWithPhysicsBehavior(
runtimeScene
);
behavior.onContactBegin(otherBehavior);
expect(behavior.contactsStartedThisFrame.length).to.be(1);
expect(behavior.contactsStartedThisFrame[0]).to.be(otherBehavior);
expect(behavior.contactsEndedThisFrame.length).to.be(0);
behavior.onContactEnd(otherBehavior);
expect(behavior.contactsStartedThisFrame.length).to.be(1);
expect(behavior.contactsStartedThisFrame[0]).to.be(otherBehavior);
expect(behavior.contactsEndedThisFrame.length).to.be(1);
expect(behavior.contactsEndedThisFrame[0]).to.be(otherBehavior);
});
it('should not add behavior to list of started contacts if the behavior is also present in the list of ended contacts', () => {
// From the user point of view the objects are staying in contact with each other.
// They would be surprised if the conditions for a contact beginning and
// end were true.
const fps = 50;
runtimeGame.setGameResolutionSize(1000, 1000);
const { behavior } = createObjectWithPhysicsBehavior(runtimeScene);
const { behavior: otherBehavior } = createObjectWithPhysicsBehavior(
runtimeScene
);
behavior.onContactEnd(otherBehavior);
expect(behavior.contactsStartedThisFrame.length).to.be(0);
expect(behavior.contactsEndedThisFrame.length).to.be(1);
expect(behavior.contactsEndedThisFrame[0]).to.be(otherBehavior);
behavior.onContactBegin(otherBehavior);
expect(behavior.contactsStartedThisFrame.length).to.be(0);
expect(behavior.contactsEndedThisFrame.length).to.be(0);
});
});
});

View File

@@ -36,7 +36,6 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<PhysicsBehavior>(),
std::make_shared<ScenePhysicsDatas>());
#if defined(GD_IDE_ONLY)
aut.AddAction("SetStatic",
_("Make the object static"),
_("Make the object immovable."),
@@ -47,8 +46,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetStatic")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetStatic");
aut.AddAction("SetDynamic",
_("Make the object dynamic"),
@@ -61,8 +59,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetDynamic")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetDynamic");
aut.AddCondition("IsDynamic",
_("The object is dynamic"),
@@ -75,8 +72,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.SetFunctionName("IsDynamic")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("IsDynamic");
aut.AddAction("SetFixedRotation",
_("Fix rotation"),
@@ -88,8 +84,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetFixedRotation")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetFixedRotation");
aut.AddAction(
"AddRevoluteJoint",
@@ -105,8 +100,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Hinge X position"))
.AddParameter("expression", _("Hinge Y position"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("AddRevoluteJoint")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("AddRevoluteJoint");
aut.AddAction("AddRevoluteJointBetweenObjects",
_("Add a hinge between two objects"),
@@ -131,8 +125,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
"",
true)
.SetDefaultValue("0")
.SetFunctionName("AddRevoluteJointBetweenObjects")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("AddRevoluteJointBetweenObjects");
aut.AddAction("ActAddGearJointBetweenObjects",
_("Add a gear between two objects"),
@@ -147,8 +140,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Ratio"), "", true)
.SetDefaultValue("1")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("AddGearJointBetweenObjects")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("AddGearJointBetweenObjects");
aut.AddAction("SetFreeRotation",
_("Make object's rotation free"),
@@ -160,8 +152,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetFreeRotation")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetFreeRotation");
aut.AddCondition("IsFixedRotation",
_("Fixed rotation"),
@@ -173,8 +164,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("IsFixedRotation")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("IsFixedRotation");
aut.AddAction("SetAsBullet",
_("Treat object like a bullet."),
@@ -187,8 +177,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAsBullet")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetAsBullet");
aut.AddAction("DontSetAsBullet",
_("Do not treat object like a bullet"),
@@ -201,8 +190,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("DontSetAsBullet")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("DontSetAsBullet");
aut.AddCondition("IsBullet",
_("Object is treated like a bullet"),
@@ -214,8 +202,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("IsBullet")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("IsBullet");
aut.AddAction("ApplyImpulse",
_("Apply an impulse"),
@@ -229,8 +216,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X component ( Newtons/Seconds )"))
.AddParameter("expression", _("Y component ( Newtons/Seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulse")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyImpulse");
aut.AddAction("ApplyImpulseUsingPolarCoordinates",
_("Apply an impulse (angle)"),
@@ -246,8 +232,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Angle"))
.AddParameter("expression", _("Impulse value ( Newton/seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulseUsingPolarCoordinates")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyImpulseUsingPolarCoordinates");
aut.AddAction(
"ApplyImpulseTowardPosition",
@@ -264,8 +249,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y position"))
.AddParameter("expression", _("Impulse value ( Newton/seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulseTowardPosition")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyImpulseTowardPosition");
aut.AddAction("ApplyForce",
_("Add a force"),
@@ -279,8 +263,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X component ( Newtons )"))
.AddParameter("expression", _("Y component ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForce")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyForce");
aut.AddAction("ApplyForceUsingPolarCoordinates",
_("Apply a force ( angle )"),
@@ -295,8 +278,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Angle"))
.AddParameter("expression", _("Length of the force ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForceUsingPolarCoordinates")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyForceUsingPolarCoordinates");
aut.AddAction(
"ApplyForceTowardPosition",
@@ -313,8 +295,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y position"))
.AddParameter("expression", _("Length of the force ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForceTowardPosition")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyForceTowardPosition");
aut.AddAction("ApplyTorque",
_("Add a torque (a rotation)"),
@@ -327,8 +308,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Torque value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyTorque")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("ApplyTorque");
aut.AddAction("SetLinearVelocity",
_("Linear velocity"),
@@ -342,8 +322,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X Coordinate"))
.AddParameter("expression", _("Y Coordinate"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetLinearVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetLinearVelocity");
aut.AddCondition(
"LinearVelocityX",
@@ -357,8 +336,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityX")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearVelocityX");
aut.AddCondition(
"LinearVelocityY",
@@ -372,8 +350,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityY")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearVelocityY");
aut.AddCondition("LinearVelocity",
_("Linear speed"),
@@ -386,8 +363,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearVelocity");
aut.AddAction("SetAngularVelocity",
_("Angular speed"),
@@ -400,8 +376,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("New value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAngularVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetAngularVelocity");
aut.AddCondition("AngularVelocity",
_("Angular speed"),
@@ -414,8 +389,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetAngularVelocity");
aut.AddCondition("LinearDamping",
_("Linear damping"),
@@ -428,8 +402,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearDamping");
aut.AddCondition("CollisionWith",
_("Collision"),
@@ -445,8 +418,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("objectList", _("Object"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("CollisionWith")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("CollisionWith");
aut.AddAction("SetLinearDamping",
_("Linear damping"),
@@ -459,8 +431,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetLinearDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetLinearDamping");
aut.AddCondition("AngularDamping",
_("Angular damping"),
@@ -473,8 +444,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetAngularDamping");
aut.AddAction("SetAngularDamping",
_("Angular damping"),
@@ -487,8 +457,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAngularDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetAngularDamping");
aut.AddAction("SetGravity",
_("Gravity"),
@@ -502,8 +471,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X Coordinate"))
.AddParameter("expression", _("Y Coordinate"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetGravity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetGravity");
aut.AddAction("SetPolygonScaleX",
_("Change the X scale of a collision polygon"),
@@ -518,8 +486,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Scale"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetPolygonScaleX")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetPolygonScaleX");
aut.AddAction("SetPolygonScaleY",
_("Change the Y scale of a collision polygon"),
@@ -534,8 +501,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Scale"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetPolygonScaleY")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("SetPolygonScaleY");
aut.AddCondition(
"GetPolygonScaleX",
@@ -549,8 +515,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleX")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetPolygonScaleX");
aut.AddCondition(
"GetPolygonScaleY",
@@ -564,8 +529,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters("number")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleY")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetPolygonScaleY");
aut.AddExpression("PolygonScaleX",
_("Collision polygon X scale"),
@@ -575,8 +539,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleX")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetPolygonScaleX");
aut.AddExpression("PolygonScaleY",
_("Collision polygon Y scale"),
@@ -586,8 +549,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleY")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetPolygonScaleY");
aut.AddExpression("LinearVelocity",
_("Linear speed"),
@@ -597,8 +559,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearVelocity");
aut.AddExpression("LinearVelocityX",
_("X component"),
@@ -608,8 +569,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityX")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearVelocityX");
aut.AddExpression("LinearVelocityY",
_("Y component"),
@@ -619,8 +579,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityY")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearVelocityY");
aut.AddExpression("AngularVelocity",
_("Angular speed"),
@@ -630,8 +589,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetAngularVelocity");
aut.AddExpression("LinearDamping",
_("Linear damping"),
@@ -641,8 +599,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
.SetFunctionName("GetLinearDamping");
aut.AddExpression("AngularDamping",
_("Angular damping"),
@@ -652,9 +609,6 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
#endif
.SetFunctionName("GetAngularDamping");
}
}

View File

@@ -778,7 +778,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"PlatformBehavior",
_("Platform"),
"Platform",
_("Flag objects as being platforms where characters can run on."),
_("Flag objects as being platforms which characters can run on."),
"",
"CppPlatform/Extensions/platformicon.png",
"PlatformBehavior",

View File

@@ -1032,6 +1032,7 @@ namespace gdjs {
) {
continue;
}
if (
gdjs.RuntimeObject.collisionTest(
this.owner,
@@ -1714,6 +1715,17 @@ namespace gdjs {
behavior._overlappedJumpThru.push(this._floorPlatform!);
behavior._setFalling();
}
// It was originally in checkTransitionBeforeY.
// The character is ignoring the floor when moving on X to be able to
// follow up a slope when moving Y (it enter inside it).
// When the current floor and the wall the character is facing is part of
// the same instance, the wall is also ignored when moving on X, but the
// wall is too high to follow and it is seen as colliding an obstacle
// from behind.
// Moving against a wall before jumping in this configuration was making
// jumps being aborted.
behavior._checkTransitionJumping();
}
beforeMovingX() {
@@ -1726,10 +1738,8 @@ namespace gdjs {
checkTransitionBeforeY(timeDelta: float) {
const behavior = this._behavior;
//Go on a ladder
// Go on a ladder
behavior._checkTransitionOnLadder();
//Jumping
behavior._checkTransitionJumping();
}
beforeMovingY(timeDelta: float, oldX: float) {

View File

@@ -1606,4 +1606,183 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
expect(object.getY()).to.be(-30);
});
});
describe('(jump against a wall)', function () {
/** @type {gdjs.RuntimeScene} */
let runtimeScene;
/** @type {gdjs.RuntimeObject} */
let object;
/** @type {gdjs.PlatformerObjectRuntimeBehavior} */
let behavior;
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: 60,
jumpSustainTime: 0.2,
useLegacyTrajectory: false,
},
],
effects: [],
});
behavior = object.getBehavior('auto1');
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
// The object is in the corner of the platform.
object.setPosition(80 - 10, 80 - 20);
});
[
{
wallBeing: 'distinct from the floor',
createPlatforms: (runtimeScene) => {
const floor = addPlatformObject(runtimeScene);
floor.setPosition(0, 80);
floor.setCustomWidthAndHeight(100, 20);
const wall = addPlatformObject(runtimeScene);
wall.setPosition(80, 0);
wall.setCustomWidthAndHeight(20, 100);
},
},
{
wallBeing: 'merged with the floor',
createPlatforms: (runtimeScene) => {
const platform = addFloorAndWallPlatformObject(runtimeScene);
platform.setPosition(0, 0);
},
},
].forEach(({ wallBeing, createPlatforms }) => {
it(`can jump while moving against a wall ${wallBeing}`, function () {
createPlatforms(runtimeScene);
// The object stays on the platform.
for (let i = 0; i < 5; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(object.getY()).to.within(60 - epsilon, 60 + epsilon);
expect(behavior.isFalling()).to.be(false);
expect(behavior.isFallingWithoutJumping()).to.be(false);
expect(behavior.isMoving()).to.be(false);
// Jump without sustain.
behavior.simulateJumpKey();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isJumping()).to.be(true);
// The object is jumping and get higher.
for (let i = 0; i < 5; ++i) {
const oldY = object.getY();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isJumping()).to.be(true);
expect(object.getX()).to.be(80 - 10);
expect(object.getY()).to.be.lessThan(oldY);
}
});
});
});
describe('(jump from slopes)', function () {
/** @type {gdjs.RuntimeScene} */
let runtimeScene;
/** @type {gdjs.RuntimeObject} */
let object;
/** @type {gdjs.PlatformerObjectRuntimeBehavior} */
let behavior;
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: 150,
maxFallingSpeed: 1500,
acceleration: 1000000,
deceleration: 1500,
maxSpeed: 2000,
// This is a very low speed relatively to other properties.
// This is not a playable configuration.
jumpSpeed: 100,
canGrabPlatforms: true,
ignoreDefaultControls: true,
slopeMaxAngle: 60,
jumpSustainTime: 0.2,
useLegacyTrajectory: false,
},
],
effects: [],
});
behavior = object.getBehavior('auto1');
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
// The object is in the slope.
object.setPosition(0, 70);
const platform = addUpSlopePlatformObject(runtimeScene);
platform.setPosition(0, 0);
});
// This is a edge case. The test can be changed if necessary.
// Usually characters jump speed is higher than the speed on Y following a
// slope.
it('can jump while moving up on a slope', function () {
// The object stays on the platform.
for (let i = 0; i < 5; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(object.getY()).to.within(70, 70 + 1);
expect(behavior.isFalling()).to.be(false);
expect(behavior.isFallingWithoutJumping()).to.be(false);
expect(behavior.isMoving()).to.be(false);
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
// Jump without sustain.
behavior.simulateJumpKey();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isJumping()).to.be(true);
// The object is jumping and is kind of sliding on the slope.
// This behavior is not expected but it avoid the character to be stuck
// into the floor in more common cases.
for (let i = 0; i < 19; ++i) {
const oldY = object.getY();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isJumping()).to.be(true);
expect(object.getY()).to.be.lessThan(oldY);
// As soon as: behavior.getCurrentJumpSpeed() - behavior.getCurrentFallSpeed()
// The character is in the falling step of the jump and it can land.
expect(behavior.isFalling()).to.be(false);
}
// The character lands.
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
expect(behavior.isOnFloor()).to.be(true);
});
});
});

View File

@@ -136,7 +136,7 @@
const addTunnelPlatformObject = (runtimeScene) => {
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
name: 'slope',
name: 'tunnel',
type: '',
behaviors: [
{
@@ -188,6 +188,68 @@
return platform;
};
/**
* @returns A platform with 2 hitboxes that can act as a floor and a wall at
* the same time.
* It can happen when a tile map collision mask object is used because all
* the platforms are part of the same object instance.
*/
const addFloorAndWallPlatformObject = (runtimeScene) => {
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
name: 'elbow',
type: '',
behaviors: [
{
type: 'PlatformBehavior::PlatformBehavior',
name: 'Platform',
canBeGrabbed: true,
},
],
effects: [],
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: { x: 0, y: 0 },
centerPoint: { x: 50, y: 50 },
points: [
{ name: 'Origin', x: 0, y: 0 },
{ name: 'Center', x: 50, y: 50 },
],
hasCustomCollisionMask: true,
customCollisionMask: [
// Wall
[
{ x: 80, y: 0 },
{ x: 80, y: 100 },
{ x: 100, y: 100 },
{ x: 100, y: 0 },
],
// Floor
[
{ x: 0, y: 80 },
{ x: 0, y: 100 },
{ x: 100, y: 100 },
{ x: 100, y: 80 },
],
],
},
],
},
],
},
],
});
runtimeScene.addObject(platform);
platform.setUnscaledWidthAndHeight(100, 300);
platform.setCustomWidthAndHeight(100, 300);
return platform;
};
const addJumpThroughPlatformObject = (runtimeScene) => {
const platform = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj2',

View File

@@ -31,8 +31,6 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.SetCategoryFullName(_("General"));
#if defined(GD_IDE_ONLY)
obj.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
obj.AddAction(
"Rectangle",
_("Rectangle"),
@@ -48,8 +46,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Top Y position"))
.AddParameter("expression", _("Right X position"))
.AddParameter("expression", _("Bottom Y position"))
.SetFunctionName("DrawRectangle")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawRectangle");
obj.AddAction("Circle",
_("Circle"),
@@ -64,8 +61,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X position of center"))
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression", _("Radius (in pixels)"))
.SetFunctionName("DrawCircle")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawCircle");
obj.AddAction("Line",
_("Line"),
@@ -84,8 +80,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X position of end point"))
.AddParameter("expression", _("Y position of end point"))
.AddParameter("expression", _("Thickness (in pixels)"))
.SetFunctionName("DrawLine")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawLine");
obj.AddAction("LineV2",
_("Line"),
@@ -103,8 +98,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("X position of end point"))
.AddParameter("expression", _("Y position of end point"))
.AddParameter("expression", _("Thickness (in pixels)"))
.SetFunctionName("drawLineV2")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawLineV2");
obj.AddAction("Ellipse",
_("Ellipse"),
@@ -121,8 +115,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression", _("The width of the ellipse"))
.AddParameter("expression", _("The height of the ellipse"))
.SetFunctionName("DrawEllipse")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawEllipse");
obj.AddAction("RoundedRectangle",
_("Rounded rectangle"),
@@ -140,8 +133,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Right X position"))
.AddParameter("expression", _("Bottom Y position"))
.AddParameter("expression", _("Radius (in pixels)"))
.SetFunctionName("DrawRoundedRectangle")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawRoundedRectangle");
obj.AddAction(
"Star",
@@ -163,8 +155,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression",
_("Inner radius (in pixels, half radius by default)"))
.AddParameter("expression", _("Rotation (in degrees)"))
.SetFunctionName("DrawStar")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawStar");
obj.AddAction("Arc",
_("Arc"),
@@ -187,8 +178,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("End angle of the arc (in degrees)"))
.AddParameter("yesorno", _("Anticlockwise"))
.AddParameter("yesorno", _("Close path"))
.SetFunctionName("DrawArc")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("DrawArc");
obj.AddAction("BezierCurve",
_("Bezier curve"),
@@ -210,8 +200,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Second Control point y"))
.AddParameter("expression", _("Destination point x"))
.AddParameter("expression", _("Destination point y"))
.SetFunctionName("drawBezierCurve")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawBezierCurve");
obj.AddAction("QuadraticCurve",
_("Quadratic curve"),
@@ -230,8 +219,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Control point y"))
.AddParameter("expression", _("Destination point x"))
.AddParameter("expression", _("Destination point y"))
.SetFunctionName("drawQuadraticCurve")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawQuadraticCurve");
obj.AddAction("BeginFillPath",
_("Begin fill path"),
@@ -248,8 +236,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("Start drawing x"))
.AddParameter("expression", _("Start drawing y"))
.SetFunctionName("beginFillPath")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("beginFillPath");
obj.AddAction("EndFillPath",
_("End fill path"),
@@ -261,8 +248,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"res/actions/endFillPath.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("endFillPath")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("endFillPath");
obj.AddAction("MovePathTo",
_("Move path drawing position"),
@@ -276,8 +262,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of start point"))
.AddParameter("expression", _("Y position of start point"))
.SetFunctionName("drawPathMoveTo")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawPathMoveTo");
obj.AddAction("PathLineTo",
_("Path line"),
@@ -294,8 +279,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of start point"))
.AddParameter("expression", _("Y position of start point"))
.SetFunctionName("drawPathLineTo")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawPathLineTo");
obj.AddAction("PathBezierCurveTo",
_("Path bezier curve"),
@@ -318,8 +302,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Second Control point y"))
.AddParameter("expression", _("Destination point x"))
.AddParameter("expression", _("Destination point y"))
.SetFunctionName("drawPathBezierCurveTo")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawPathBezierCurveTo");
obj.AddAction("PathArc",
_("Path arc"),
@@ -342,8 +325,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Start angle"))
.AddParameter("expression", _("End angle"))
.AddParameter("yesorno", _("Anticlockwise"))
.SetFunctionName("drawPathArc")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawPathArc");
obj.AddAction("PathQuadraticCurveTo",
_("Path quadratic curve"),
@@ -363,8 +345,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Control point y"))
.AddParameter("expression", _("Destination point x"))
.AddParameter("expression", _("Destination point y"))
.SetFunctionName("drawPathQuadraticCurveTo")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("drawPathQuadraticCurveTo");
obj.AddAction("closePath",
_("Close Path"),
@@ -377,8 +358,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"res/actions/closePath.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("closePath")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("closePath");
obj.AddScopedAction("ClearShapes",
_("Clear shapes"),
@@ -403,8 +383,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("yesorno", _("Clear between each frame"), "", true)
.SetDefaultValue("yes")
.SetFunctionName("SetClearBetweenFrames")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("SetClearBetweenFrames");
obj.AddCondition(
"ClearBetweenFrames",
@@ -416,8 +395,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"res/conditions/visibilite.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("IsClearedBetweenFrames")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("IsClearedBetweenFrames");
obj.AddAction("FillColor",
_("Fill color"),
@@ -429,8 +407,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("color", _("Fill color"))
.SetFunctionName("SetFillColor")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("SetFillColor");
obj.AddExpression("FillColorRed",
_("Filing color red component"),
@@ -438,8 +415,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/actions/color.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetFillColorR")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetFillColorR");
obj.AddExpression("FillColorGreen",
_("Filing color green component"),
@@ -447,8 +423,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/actions/color.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetFillColorG")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetFillColorG");
obj.AddExpression("FillColorBlue",
_("Filing color blue component"),
@@ -456,8 +431,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/actions/color.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetFillColorB")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetFillColorB");
obj.AddAction("OutlineColor",
_("Outline color"),
@@ -469,8 +443,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("color", _("Color"))
.SetFunctionName("SetOutlineColor")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("SetOutlineColor");
obj.AddExpression("OutlineColorRed",
_("Outline color red component"),
@@ -478,8 +451,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/actions/color.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetOutlineColorR")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineColorR");
obj.AddExpression("OutlineColorGreen",
_("Outline color green component"),
@@ -487,8 +459,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/actions/color.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetOutlineColorG")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineColorG");
obj.AddExpression("OutlineColorBlue",
_("Outline color blue component"),
@@ -496,8 +467,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/actions/color.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetOutlineColorB")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineColorB");
obj.AddAction("OutlineSize",
_("Outline size"),
@@ -510,8 +480,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetOutlineSize")
.SetGetter("GetOutlineSize")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetGetter("GetOutlineSize");
obj.AddCondition("OutlineSize",
_("Outline size"),
@@ -523,8 +492,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetOutlineSize")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineSize");
obj.AddExpression("OutlineSize",
_("Outline size"),
@@ -532,8 +500,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/conditions/outlineSize.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetOutlineSize")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineSize");
obj.AddAction(
"FillOpacity",
@@ -547,8 +514,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetFillOpacity")
.SetGetter("GetFillOpacity")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetGetter("GetFillOpacity");
obj.AddCondition("FillOpacity",
_("Fill opacity"),
@@ -560,8 +526,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetFillOpacity")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetFillOpacity");
obj.AddExpression("FillOpacity",
_("Filling opacity"),
@@ -569,8 +534,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/conditions/opacity.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetFillOpacity")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetFillOpacity");
obj.AddAction("OutlineOpacity",
_("Outline opacity"),
@@ -583,8 +547,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.UseStandardOperatorParameters("number")
.SetFunctionName("SetOutlineOpacity")
.SetGetter("GetOutlineOpacity")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetGetter("GetOutlineOpacity");
obj.AddCondition("OutlineOpacity",
_("Outline opacity"),
@@ -596,8 +559,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.UseStandardRelationalOperatorParameters("number")
.SetFunctionName("GetOutlineOpacity")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineOpacity");
obj.AddExpression("OutlineOpacity",
_("Outline opacity"),
@@ -605,8 +567,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"",
"res/conditions/opacity.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("GetOutlineOpacity")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("GetOutlineOpacity");
obj.AddAction(
"UseRelativeCoordinates",
@@ -620,8 +581,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("yesorno", _("Use relative coordinates?"), "", false)
.SetDefaultValue("true")
.SetFunctionName("setCoordinatesRelative")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("setCoordinatesRelative");
obj.AddCondition(
"AreCoordinatesRelative",
@@ -633,8 +593,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
"res/conditions/position.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.SetFunctionName("AreCoordinatesRelative")
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
.SetFunctionName("AreCoordinatesRelative");
obj.AddAction("Scale",
_("Scale"),

File diff suppressed because one or more lines are too long

View File

@@ -29,8 +29,7 @@ void DeclareSystemInfoExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/systeminfoicon.png",
"CppPlatform/Extensions/systeminfoicon.png")
.SetFunctionName("SystemInfo::IsMobile")
.SetIncludeFile("SystemInfo/SystemInfoTools.h");
.SetFunctionName("SystemInfo::IsMobile");
extension
.AddCondition("IsWebGLSupported",
@@ -43,8 +42,7 @@ void DeclareSystemInfoExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/systeminfoicon.png")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SystemInfo::IsWebGLSupported")
.SetIncludeFile("SystemInfo/SystemInfoTools.h");
.SetFunctionName("SystemInfo::IsWebGLSupported");
extension
.AddCondition(

View File

@@ -29,9 +29,6 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/textentry.png")
.SetCategoryFullName(_("Advanced"));
#if defined(GD_IDE_ONLY)
obj.SetIncludeFile("TextEntryObject/TextEntryObject.h");
obj.AddAction("String",
_("Text in memory"),
_("Modify text in memory of the object"),
@@ -43,8 +40,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "TextEntry")
.UseStandardOperatorParameters("string")
.SetFunctionName("SetString")
.SetGetter("GetString")
.SetIncludeFile("TextEntryObject/TextEntryObject.h");
.SetGetter("GetString");
obj.AddCondition("String",
_("Text in memory"),
@@ -56,8 +52,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "TextEntry")
.UseStandardRelationalOperatorParameters("string")
.SetFunctionName("GetString")
.SetIncludeFile("TextEntryObject/TextEntryObject.h");
.SetFunctionName("GetString");
obj.AddAction(
"Activate",
@@ -71,8 +66,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "TextEntry")
.AddParameter("yesorno", _("Activate"))
.SetFunctionName("Activate")
.SetIncludeFile("TextObject/TextObject.h");
.SetFunctionName("Activate");
obj.AddCondition("Activated",
_("Text input"),
@@ -83,8 +77,7 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/textentryicon.png")
.AddParameter("object", _("Object"), "TextEntry")
.SetFunctionName("IsActivated")
.SetIncludeFile("TextObject/TextObject.h");
.SetFunctionName("IsActivated");
obj.AddStrExpression("String",
_("Text entered with keyboard"),
@@ -92,7 +85,5 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
_("Text entered with keyboard"),
"res/texteicon.png")
.AddParameter("object", _("Object"), "TextEntry")
.SetFunctionName("GetString")
.SetIncludeFile("TextObject/TextObject.h");
#endif
.SetFunctionName("GetString");
}

View File

@@ -47,6 +47,7 @@ namespace gdjs {
const isTextArea = this._object.getInputType() === 'text area';
this._input = document.createElement(isTextArea ? 'textarea' : 'input');
this._input.style.border = '1px solid black';
this._input.autocomplete = 'off';
this._input.style.borderRadius = '0px';
this._input.style.backgroundColor = 'white';
this._input.style.position = 'absolute';

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