Compare commits

...

107 Commits

Author SHA1 Message Date
D8H
6cda5d08be Fix missing flippingZ accessors (#6968)
- Don't show in changelog
2024-09-17 16:00:14 +02:00
D8H
1a3a27b73b Fix custom object flipping (#6967)
Do not show in changelog
2024-09-17 15:15:25 +02:00
Clément Pasteau
96d912a6f2 Fix store UI (wrong centering of items) (#6965) 2024-09-17 14:23:28 +02:00
D8H
ed3acd5f0d Layout custom objects children according to their anchors in the editor (#6939) 2024-09-17 12:43:16 +02:00
AlexandreS
3f269206d1 Bump newIDE version (#6963) 2024-09-17 12:37:04 +02:00
github-actions[bot]
76b5aefdbc Update translations [skip ci] (#6961) 2024-09-17 12:36:38 +02:00
AlexandreS
9ef7af803c Change teacher resources list to include upcoming lessons (#6956) 2024-09-17 12:27:45 +02:00
D8H
c77f9b9e0c Fix a crash in the new behavior dialog (#6962)
- The crash happened when the extensions didn't load as expected.
2024-09-17 12:18:26 +02:00
D8H
035ddb8a7a Fix hot reload of object variables in object instances (#6958) 2024-09-17 12:14:10 +02:00
AlexandreS
e7dac1bafc Tilemap improvements (#6957)
- Improve performance display when painting
- Fix painting when tilemap is rotated
- Allow atlas size to be something else than a tile size multiple and ignore last column and row
- Display error message only when the the tile size is greater than the atlas image
- Do not crash preview if tilemap badly configured
- Add object name in actions and conditions
2024-09-17 11:18:30 +02:00
github-actions[bot]
54f00e7c57 Update translations [skip ci] (#6951)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-09-17 10:06:45 +02:00
Clément Pasteau
0bf9dae2b0 Fix icon of credit packages (#6960)
Do not show in changelog
2024-09-17 10:06:18 +02:00
Florian Rival
428aac8ab0 Fix sprite animation lists and selection checkbox
Don't show in changelog
2024-09-15 17:44:21 +02:00
Florian Rival
9391fc2841 Remove warnings during C++ emscripten compilation 2024-09-13 15:51:55 +02:00
Florian Rival
cea34337c6 Backport improvements made for objects panel (#6955)
Do not show in changelog
2024-09-13 15:26:49 +02:00
Clément Pasteau
dc45f3dae5 Allow changing Opacity & Flip directly in Instance Properties Panel (#6935)
* This allows playing around with instances directly on the canvas instead of relying on the actions to flip or change opacity and needing to start a preview
2024-09-13 13:39:03 +02:00
AlexandreS
0ca26a865e Authenticate plans and pricing systems fetch calls (#6952) 2024-09-13 11:45:23 +02:00
Clément Pasteau
1bce13f326 Bump to 5.4.212 (#6950) 2024-09-12 16:42:52 +02:00
github-actions[bot]
34f8f5750a Update translations [skip ci] (#6943)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-09-12 16:16:06 +02:00
AlexandreS
3b9a612094 Fix tilemap object edge cases causing crashes (#6945) 2024-09-12 15:49:02 +02:00
Clément Pasteau
3a84ed7c89 Fix object images not displaying on quick customization (#6949)
Do not show in changelog
2024-09-12 15:07:22 +02:00
AlexandreS
ef604fd442 Display active pricing systems only (#6948)
Don't show in changelog
2024-09-12 12:09:13 +02:00
Clément Pasteau
d88dc4772f Catch callback functions of Firebase preventing JS crash (#6946)
Only show in developer changelog
2024-09-11 17:32:49 +02:00
AlexandreS
02d40a1d52 Fix: Catch errors when handling electron browser window (#6944) 2024-09-11 09:13:40 +02:00
Clément Pasteau
35082825d4 Fix triggering the onWheel only when an input is focused (#6940) 2024-09-10 17:35:17 +02:00
Clément Pasteau
6a7c3daa8e Fix wheel increment (#6938) 2024-09-09 14:15:38 +02:00
Clément Pasteau
95af02bada Bump to 5.4.211 (#6937) 2024-09-09 13:56:35 +02:00
Clément Pasteau
30516a903e Fix instance number properties which could not be saved to 0 (#6936)
* Also add back the ability to use keyboard arrows and mouse wheel to control the value finely
2024-09-09 13:56:19 +02:00
Florian Rival
762f7ca19c Avoid unnecessary recompilation of many C++ files at each build
Only show in developer changelog
2024-09-09 13:39:34 +02:00
Florian Rival
852bf78c81 Show a loading texture for Panel Sprite instead of a error texture (#6931) 2024-09-09 13:04:15 +02:00
github-actions[bot]
b0da0cee34 Update translations [skip ci] (#6925)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-09-09 11:13:35 +02:00
Aurélien Vivet
73771f938b Fix checkered background in collision and points editor (#6928) 2024-09-06 16:20:59 +02:00
Florian Rival
b9dbe6dbb5 Fix exception when changing an external tilemap file after it was initially missing (#6929) 2024-09-06 16:05:57 +02:00
Florian Rival
0c2341c6e5 Fix crash in the scene editor when changing the Panel Sprite tiled/stretched option (#6926) 2024-09-05 18:25:13 +02:00
Florian Rival
e9b4de2ca9 Fix tilemap instance error when no image set (#6923)
Only show in developer changelog
2024-09-05 16:08:59 +02:00
Clément Pasteau
75a4114ce8 Fix extension generation for docs (#6924) 2024-09-05 16:04:48 +02:00
Clément Pasteau
20abb9b45a Bump to 5.4.210 (#6922) 2024-09-05 11:54:52 +02:00
github-actions[bot]
56436fd44a Update translations [skip ci] (#6921)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-09-05 11:54:35 +02:00
AlexandreS
0dd5fc55c9 Improve subscriptions dialog to display current plan at the top (#6918)
* Mainly useful for old subscriptions that are not available in the list of available ones anymore.
2024-09-05 09:37:43 +02:00
github-actions[bot]
93db4cb508 Update translations [skip ci] (#6919)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2024-09-05 08:43:06 +02:00
D8H
f7888abf45 Allow custom objects to use the anchor behavior on their children (#6917) 2024-09-04 19:00:49 +02:00
github-actions[bot]
c8144da704 Update translations [skip ci] (#6909)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-09-04 15:25:50 +02:00
Giordane Oliveira
832e8cd593 Update AdMob banner so they cover part of the game screen instead of reducing it (#6900)
* In most games, it's better to design the game with the size of the ad banner in mind - which will cover either part of the top or bottom of the screen. Previously, the game size was reduced which was making it difficult to properly design a game and would introduce bad looking black borders.
2024-09-04 09:14:12 +02:00
AlexandreS
ef66a9f1a4 Add support for math formula in instance properties panel fields (#6914)
* For example, you can enter `100 + 50` or `100 + 70/2` or `ToDeg(sin(2.3))` in the instance editor fields
2024-09-03 18:41:38 +02:00
Florian Rival
5efbaa8c58 Add automated crash reporting from previews (#6915)
* This will allow to detect any bugs or crash in the game engine without relying on manual reports from users on GitHub. Note that exceptions and errors in JavaScript code blocks won't be reported. This can be deactivated in preferences if you prefer not to have GDevelop send these crash reports at all.
2024-09-03 16:52:53 +02:00
AlexandreS
ecbf38ccda Remove effects tab (and actions and conditions) wrongly shown for Tilemap objects (#6916) 2024-09-03 16:33:57 +02:00
AlexandreS
b3fcfc3f55 Avoid exponential loop when scrolling out of view on the Scene editor (#6912) 2024-09-03 09:33:45 +02:00
D8H
a515836add Avoid to duplicate custom object data in the project files (#6904)
- Fix hot-reload of sprite object animations
2024-09-02 18:52:27 +02:00
D8H
a7c81b47b2 Fix text input not properly deleted when used in a Custom Object (#6910) 2024-09-02 17:59:39 +02:00
D8H
0f22e462ad Keep clearing object lists before the events too (#6913)
- Don't show in changelog
2024-09-02 17:43:47 +02:00
AlexandreS
e6e4d9048f Add action to set Text object font (#6911) 2024-09-02 16:25:41 +02:00
github-actions[bot]
12f5f95d0c Update translations [skip ci] (#6902)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-09-02 13:53:57 +02:00
D8H
c52168a967 Fix memory clearing/leak when a scene is paused or stopped (#6123) 2024-09-02 13:52:37 +02:00
AlexandreS
1e33a13cc5 Improve tilemap preview rendering (#6908)
- Remove FPS limitation
- Avoid useless computations
2024-09-02 12:10:43 +02:00
Vladyslav Pohorielov
505debd60c Add expressions to get Spine point attachments positions (#6907) 2024-09-02 10:35:30 +02:00
Aurélien Vivet
e3b7109154 Add a default shortcut for adding a comment in the Events Sheet (#6879) 2024-09-02 09:22:13 +02:00
D8H
9e25899d3e Remove an "only" in GDJS tests (#6906)
- Don't show in changelog
2024-08-31 00:28:10 +02:00
AlexandreS
87cb8f0d47 Activate user after creating it (#6901)
Don't show in changelog
2024-08-30 09:57:38 +02:00
github-actions[bot]
481c6da992 Update translations [skip ci] (#6886)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-08-30 09:28:33 +02:00
AlexandreS
7cbebbb82f Fix some tilemap UX paintpoints (#6899)
- When using a mouse, use left click only to paint. Middle click can be used to pan the view.
- Fix issues with undo/redo tile setting
2024-08-29 20:08:05 +02:00
AlexandreS
fcf668788b Add interface for teacher to manage education users (#6891) 2024-08-29 18:42:25 +02:00
D8H
0cc844a77f Make the experimental custom object visual editor accessible (#6897)
* This is an *experimental**, *still work-in-progress*, editor to build objects inside GDevelop - from small, reusable UI objects to larger parts of your game.
2024-08-29 18:33:25 +02:00
Clément Pasteau
a234d9bd35 Show game ads earnings (#6896)
* You can now see the earnings of the games you've published on gd.games, thanks to ads, at the top of your Games dashboard
* You can cash out that amount if you reach the threshold or exchange it with GDevelop credits
2024-08-29 18:27:40 +02:00
D8H
465a6ce2ab Fix custom objects hot-reloading (#6887)
- Don't show in changelog
2024-08-29 18:13:54 +02:00
D8H
7e2e19eb33 Improve tile map collision precision (#6895) 2024-08-29 18:13:08 +02:00
D8H
95101763f7 Hide the drop-down list about quick customization visibility (#6893) 2024-08-28 16:59:24 +02:00
D8H
d4bd5fc671 Hide required behavior properties in the object editor (#6889) 2024-08-28 14:07:36 +02:00
D8H
c7fcf48ba5 Hide a duplicated expression to get the text of a text input (#6892) 2024-08-28 14:07:23 +02:00
D8H
8926d4406f Fix to forbid default parameters from being dragged and dropped (#6888) 2024-08-27 19:20:45 +02:00
Clément Pasteau
9ed2173038 Multiplayer game host migration (#6878)
* Host of a multiplayer game is now automatically migrated to a new one, picked from the list of players, with the lowest ping.
* A new condition is available "Is host migrating" in order to adapt the game while the migration happens. This can take up to a few seconds. If your game has no interactions between players, you can leave it like this. Otherwise, you may want to pause the game while the host is changing!
* A new action is available to configure the lobby game to end instead of migrating when the host leaves. (This is useful if you prefer the host not changing during your game)
2024-08-27 14:30:36 +02:00
github-actions[bot]
2fc3bc337f Update translations [skip ci] (#6885)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-08-27 11:49:01 +02:00
Clément Pasteau
0b7cac79ef Allow changing Sync Rate of objects in a multiplayer game (#6884)
* Default sync rate of objects is now set to 30 times per second (was 60 before, but 30 is enough for most games)
* A new action can be used to update this value depending on the type of your game, at the cost of bandwidth. Fast-paced competitive? go for 60. Slow turn-based game? 10 is probably enough, or even less!
2024-08-26 18:27:27 +02:00
D8H
8721c0099e [Physics2] Merge the 2 world scale properties into one (#6865) 2024-08-26 15:16:01 +02:00
Aurélien Vivet
4453eee3b9 Fix default values for the Adjustement effect (#6883) 2024-08-26 14:11:44 +02:00
github-actions[bot]
0215ab7dbb Update translations [skip ci] (#6874)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-08-26 14:10:00 +02:00
AlexandreS
87f6d5b99f Fix update notifications translations on Desktop app (#6882) 2024-08-26 10:57:39 +02:00
D8H
a440b16f84 Fix a memory issue with default custom object configurations (#6881)
- Don't show in changelog
2024-08-23 20:30:45 +02:00
D8H
f3822ba0df Allow to center objects with the anchor behavior (#6880) 2024-08-23 19:02:31 +02:00
D8H
6c5813affd Fix hot-reload for variables (#6877) 2024-08-23 15:03:40 +02:00
D8H
be4fe62bb6 Allow to override custom objects default property values or to follow them (#6861) 2024-08-23 14:12:13 +02:00
AlexandreS
0a29999894 Add possibility to drop variable after last item (#6875) 2024-08-22 09:59:07 +02:00
Florian Rival
e8ac41f37e Fix formatting 2024-08-21 13:09:04 +02:00
Florian Rival
70657ae334 Fix stretched button and wording
Don't show in changelog
2024-08-21 11:24:54 +02:00
Arthur Pacaud (arthuro555)
39ae2d4852 Replace electron-is with electron-is-dev (#6852)
Only show in developer changelog
2024-08-21 09:32:38 +02:00
github-actions[bot]
39815bfe6c Update translations [skip ci] (#6872)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-08-21 09:17:42 +02:00
Florian Rival
9ff8db25dd Fix missing version file for C++ tests (#6873)
Don't show in changelog
2024-08-20 23:20:39 +02:00
Florian Rival
1da887f656 Bump newIDE version (#6871) 2024-08-20 18:25:06 +02:00
github-actions[bot]
e88ae0a7a9 Update translations [skip ci] (#6869)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-08-20 17:56:54 +02:00
Florian Rival
7ae74990b2 Add callouts on quick customization 2024-08-20 15:42:50 +02:00
Florian Rival
22ea1ce42c Fade in asset previews and animate quick publishing illustration
Don't show in changelog
2024-08-20 12:53:56 +02:00
D8H
a54367e360 Remove unused imports (#6870)
- Don't show in changelog
2024-08-20 10:57:32 +02:00
D8H
011abaf808 Allow drag and drop in the extension function parameter editor (#6847) 2024-08-20 10:29:52 +02:00
Florian Rival
a606a6567a Avoid useless loaders in asset store 2024-08-19 23:41:27 +02:00
Florian Rival
c9a6b88422 Fix scrolling wrongly retained between steps of Quick Customization dialog
Don't show in changelog
2024-08-19 17:59:25 +02:00
AlexandreS
e7f7fb1583 Fix having to select another function to reopen the same function on mobile in the extension editor (#6868) 2024-08-19 16:09:53 +02:00
github-actions[bot]
3cbb6644d4 Update translations [skip ci] (#6835)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-08-19 15:09:55 +02:00
AlexandreS
d34db53e09 Fix extension's behavior and object properties lists not scrolling on mobile (#6866) 2024-08-19 13:43:26 +02:00
Florian Rival
9c62a5e0f1 Fix unserialization of quick customization settings
Also remove VersionPriv.h from source control

Don't show in changelog
2024-08-19 12:13:12 +02:00
D8H
4b04101638 Allow custom objects and behaviors to use resources from properties (#6862)
Only show in developer changelog
2024-08-18 21:22:35 +02:00
Florian Rival
ad31a7843a Add VersionPriv.h to git ignored files [skip ci]
Don't show in changelog
2024-08-18 18:15:29 +02:00
Florian Rival
57371c3759 Add "Quick Customization" in the Get Started page (#6856)
* Choose a starter game and quickly replace objects and tweak their behaviors. The game is then automatically published so it can be shared: this is perfect for new users to get a taste of how a game works and make their own remix of a game before going further.
2024-08-18 18:04:53 +02:00
Vladyslav Pohorielov
6e7fc75e75 Fix GDevelop project files so they are versioned with the same version as the editor (#6859)
Only show in developer changelog
2024-08-14 21:10:11 +02:00
D8H
65653c92d6 Fix asset packs not opening from the home page (#6855)
- Don't show in changelog
2024-08-09 18:59:10 +02:00
D8H
a4106b7f79 Add an option in objects context menu to quickly swap assets of Sprites and 3D models (#6840)
* This is perfect for quickly prototyping or trying new game art
* Animation of the object that are missing in the asset are replaced by a copy of the 1st asset animation. 3D model volume is adjusted to stay the same while keeping asset proportions.
2024-08-09 13:40:26 +02:00
Florian Rival
3e9f2f3f3a Update README [skip ci] [ci skip] 2024-08-08 19:35:33 +02:00
AlexandreS
45f25df292 Fix storybook and add story for AsyncSemiControlledTextField (#6854) 2024-08-08 15:09:49 +02:00
AlexandreS
55eddb4972 Rework education marketing section (#6853)
Don't show in changelog
2024-08-08 13:45:17 +02:00
592 changed files with 27659 additions and 11450 deletions

View File

@@ -107,7 +107,7 @@
"description": "Define a parameter in a GDevelop extension definition.",
"prefix": "gdparam",
"body": [
".addParameter('${1|string,expression,object,behavior,yesorno,stringWithSelector,scenevar,globalvar,objectvar,objectList,objectListWithoutPicking,color,key,sceneName,file,layer,relationalOperator,operator,trueorfalse,musicfile,soundfile,police,mouse,passwordjoyaxis,camera,objectPtr,forceMultiplier|}', '${2:Parameter description}', '${3:Optional parameter data}', /*parameterIsOptional=*/${4|false,true|})"
".addParameter('${1|string,expression,object,behavior,yesorno,stringWithSelector,scenevar,globalvar,objectvar,objectList,objectListWithoutPicking,color,key,sceneName,file,layer,relationalOperator,operator,trueorfalse,musicfile,soundfile,mouse,passwordjoyaxis,camera,objectPtr,forceMultiplier|}', '${2:Parameter description}', '${3:Optional parameter data}', /*parameterIsOptional=*/${4|false,true|})"
]
},
"Add code only parameter": {

View File

@@ -60,7 +60,7 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND NOT WIN32 AND CMAKE_COMPILER_IS_
endif()
#Activate C++11
set(CMAKE_CXX_STANDARD 11) # Upgrading to C++17 would need to remove usage of bind2nd (should be easy).
set(CMAKE_CXX_STANDARD 11) # Upgrading to C++17 should be tried.
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Mark some warnings as errors

View File

@@ -11,6 +11,11 @@ set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1)
set(GDCORE_include_dir ${GD_base_dir}/Core PARENT_SCOPE)
set(GDCORE_lib_dir ${GD_base_dir}/Binaries/Output/${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME} PARENT_SCOPE)
# Create VersionPriv.h - only useful for testing.
if (NOT EMSCRIPTEN)
file(WRITE "${GD_base_dir}/Core/GDCore/Tools/VersionPriv.h" "#define GD_VERSION_STRING \"0.0.0-0\"")
endif()
# Dependencies on external libraries:
#

View File

@@ -42,14 +42,15 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
const vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t relationalOperatorIndex = instrInfos.parameters.size();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.size();
std::size_t relationalOperatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters[i].GetType() == "relationalOperator")
if (instrInfos.parameters.GetParameter(i).GetType() == "relationalOperator") {
relationalOperatorIndex = i;
}
}
// Ensure that there is at least one parameter after the relational operator
if (relationalOperatorIndex + 1 >= instrInfos.parameters.size()) {
if (relationalOperatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -76,11 +77,11 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
/**
* @brief Generate a relational operation
*
*
* @param relationalOperator the operator
* @param lhs the left hand operand
* @param rhs the right hand operand
* @return gd::String
* @return gd::String
*/
gd::String EventsCodeGenerator::GenerateRelationalOperation(
const gd::String& relationalOperator,
@@ -122,14 +123,16 @@ gd::String EventsCodeGenerator::GenerateOperatorCall(
const gd::String& callStartString,
const gd::String& getterStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.size();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.size();
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters[i].GetType() == "operator") operatorIndex = i;
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
}
}
// Ensure that there is at least one parameter after the operator
if (operatorIndex + 1 >= instrInfos.parameters.size()) {
if (operatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -191,14 +194,16 @@ gd::String EventsCodeGenerator::GenerateCompoundOperatorCall(
const vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.size();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.size();
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters[i].GetType() == "operator") operatorIndex = i;
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
}
}
// Ensure that there is at least one parameter after the operator
if (operatorIndex + 1 >= instrInfos.parameters.size()) {
if (operatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -242,14 +247,16 @@ gd::String EventsCodeGenerator::GenerateMutatorCall(
const vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.size();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.size();
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters[i].GetType() == "operator") operatorIndex = i;
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
}
}
// Ensure that there is at least one parameter after the operator
if (operatorIndex + 1 >= instrInfos.parameters.size()) {
if (operatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -316,7 +323,7 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
// Insert code only parameters and be sure there is no lack of parameter.
while (condition.GetParameters().size() < instrInfos.parameters.size()) {
while (condition.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = condition.GetParameters();
parameters.push_back(gd::Expression(""));
condition.SetParameters(parameters);
@@ -324,13 +331,13 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter =
condition.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
instrInfos.parameters[pNb].GetExtraInfo();
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
@@ -353,7 +360,7 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
if (instrInfos.IsObjectInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
if (!objectName.empty() && !instrInfos.parameters.empty()) {
if (!objectName.empty() && instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
@@ -381,7 +388,7 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.size() >= 2) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = condition.GetParameter(0).GetPlainString();
const gd::String &behaviorName =
condition.GetParameter(1).GetPlainString();
@@ -539,7 +546,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
: instrInfos.codeExtraInformation.functionCallName;
// Be sure there is no lack of parameter.
while (action.GetParameters().size() < instrInfos.parameters.size()) {
while (action.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = action.GetParameters();
parameters.push_back(gd::Expression(""));
action.SetParameters(parameters);
@@ -547,12 +554,12 @@ gd::String EventsCodeGenerator::GenerateActionCode(
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
instrInfos.parameters[pNb].GetExtraInfo();
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
@@ -577,7 +584,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (instrInfos.IsObjectInstruction()) {
gd::String objectName = action.GetParameter(0).GetPlainString();
if (!instrInfos.parameters.empty()) {
if (instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
@@ -605,7 +612,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.size() >= 2) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = action.GetParameter(0).GetPlainString();
const gd::String &behaviorName = action.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
@@ -821,7 +828,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
metadata.GetType() == "spineResource" ||
// Deprecated, old parameter names:
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
metadata.GetType() == "soundfile" || metadata.GetType() == "police") {
metadata.GetType() == "soundfile") {
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.GetType() == "mouse") {
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
@@ -863,7 +870,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
vector<gd::String> EventsCodeGenerator::GenerateParametersCodes(
const vector<gd::Expression>& parameters,
const vector<gd::ParameterMetadata>& parametersInfo,
const ParameterMetadataContainer& parametersInfo,
EventsCodeGenerationContext& context,
std::vector<std::pair<gd::String, gd::String> >*
supplementaryParametersTypes) {
@@ -1000,7 +1007,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
output += "\n" + scopeBegin + "\n" + declarationsCode + "\n" +
eventCoreCode + "\n" + scopeEnd + "\n";
if (event.HasVariables()) {
GetProjectScopedContainers().GetVariablesContainersList().Pop();
}
@@ -1100,10 +1107,10 @@ gd::String EventsCodeGenerator::GenerateFreeCondition(
// Add logical not if needed
bool conditionAlreadyTakeCareOfInversion = false;
for (std::size_t i = 0; i < instrInfos.parameters.size();
for (std::size_t i = 0; i < instrInfos.parameters.GetParametersCount();
++i) // Some conditions already have a "conditionInverted" parameter
{
if (instrInfos.parameters[i].GetType() == "conditionInverted")
if (instrInfos.parameters.GetParameter(i).GetType() == "conditionInverted")
conditionAlreadyTakeCareOfInversion = true;
}
if (!conditionAlreadyTakeCareOfInversion && conditionInverted)
@@ -1124,7 +1131,7 @@ gd::String EventsCodeGenerator::GenerateObjectCondition(
// Prepare call
// Add a static_cast if necessary
gd::String objectFunctionCallNamePart =
(!instrInfos.parameters[0].GetExtraInfo().empty())
(!instrInfos.parameters.GetParameter(0).GetExtraInfo().empty())
? "static_cast<" + objInfo.className + "*>(" +
GetObjectListName(objectName, context) + "[i])->" +
instrInfos.codeExtraInformation.functionCallName

View File

@@ -128,7 +128,7 @@ class GD_CORE_API EventsCodeGenerator {
*/
std::vector<gd::String> GenerateParametersCodes(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersInfo,
const ParameterMetadataContainer& parametersInfo,
EventsCodeGenerationContext& context,
std::vector<std::pair<gd::String, gd::String> >*
supplementaryParametersTypes = 0);
@@ -528,7 +528,7 @@ protected:
parameter -> string
* - operator : Used to update a value using a setter and a getter -> string
* - key, mouse, objectvar, scenevar, globalvar, password, musicfile,
soundfile, police -> string
soundfile -> string
* - trueorfalse, yesorno -> boolean ( See GenerateTrue/GenerateFalse ).
*
* <br><br>
@@ -849,7 +849,7 @@ protected:
instructionUniqueIds; ///< The unique ids generated for instructions.
size_t eventsListNextUniqueId; ///< The next identifier to use for an events
///< list function name.
gd::DiagnosticReport* diagnosticReport;
};

View File

@@ -430,11 +430,11 @@ gd::String ExpressionCodeGenerator::GenerateParametersCodes(
size_t nonCodeOnlyParameterIndex = 0;
gd::String parametersCode;
for (std::size_t i = initialParameterIndex;
i < expressionMetadata.parameters.size();
i < expressionMetadata.GetParameters().GetParametersCount();
++i) {
if (i != initialParameterIndex) parametersCode += ", ";
auto& parameterMetadata = expressionMetadata.parameters[i];
auto& parameterMetadata = expressionMetadata.GetParameters().GetParameter(i);
if (!parameterMetadata.IsCodeOnly()) {
if (nonCodeOnlyParameterIndex < parameters.size()) {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),

View File

@@ -182,10 +182,10 @@ void EventsListSerialization::UpdateInstructionsFromGD2x(
// Common updates for some parameters
const std::vector<gd::Expression>& parameters = instr.GetParameters();
for (std::size_t j = 0;
j < parameters.size() && j < metadata.parameters.size();
j < parameters.size() && j < metadata.parameters.GetParametersCount();
++j) {
if (metadata.parameters[j].GetType() == "relationalOperator" ||
metadata.parameters[j].GetType() == "operator") {
if (metadata.parameters.GetParameter(j).GetType() == "relationalOperator" ||
metadata.parameters.GetParameter(j).GetType() == "operator") {
if (j == parameters.size() - 1) {
std::cout << "ERROR: No more parameters after a [relational]operator "
"when trying to update an instruction from GD2.x";

View File

@@ -24,18 +24,23 @@
namespace gd {
SpriteObject::SpriteObject()
: updateIfNotVisible(false) {}
: updateIfNotVisible(false),
preScale(1) {}
SpriteObject::~SpriteObject(){};
void SpriteObject::DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) {
updateIfNotVisible = element.GetBoolAttribute("updateIfNotVisible", true);
preScale = element.GetDoubleAttribute("preScale", 1);
animations.UnserializeFrom(element);
}
void SpriteObject::DoSerializeTo(gd::SerializerElement& element) const {
element.SetAttribute("updateIfNotVisible", updateIfNotVisible);
if (preScale != 1) {
element.SetAttribute("preScale", preScale);
}
animations.SerializeTo(element);
}

View File

@@ -82,6 +82,23 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
*/
bool GetUpdateIfNotVisible() const { return updateIfNotVisible; }
/**
* \brief Return the scale applied to object to evaluate the default dimensions.
*/
double GetPreScale() { return preScale; }
/**
* \brief Set the scale applied to object to evaluate the default dimensions.
*
* Its value must be strictly positive.
*/
void SetPreScale(double preScale_) {
if (preScale_ <= 0) {
return;
}
preScale = preScale_;
}
private:
void DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) override;
@@ -92,6 +109,7 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
bool updateIfNotVisible; ///< If set to true, ask the game engine to play
///< object animation even if hidden or far from
///< the screen.
double preScale;
};
} // namespace gd

View File

@@ -37,7 +37,8 @@ BehaviorMetadata::BehaviorMetadata(
className(className_),
iconFilename(icon24x24),
instance(instance_),
sharedDatasInstance(sharedDatasInstance_) {
sharedDatasInstance(sharedDatasInstance_),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {
SetFullName(gd::String(fullname_));
SetDescription(gd::String(description_));
SetDefaultName(gd::String(defaultName_));
@@ -424,7 +425,7 @@ std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetProperties() c
return instance->GetProperties();
}
gd::BehaviorsSharedData* BehaviorMetadata::GetSharedDataInstance() const {
gd::BehaviorsSharedData* BehaviorMetadata::GetSharedDataInstance() const {
return sharedDatasInstance.get();
}
@@ -440,12 +441,18 @@ std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetSharedProperti
const std::vector<gd::String>& BehaviorMetadata::GetRequiredBehaviorTypes() const {
requiredBehaviors.clear();
for (auto& property : Get().GetProperties()) {
if (!instance) {
return requiredBehaviors;
}
for (auto& property : instance->GetProperties()) {
const String& propertyName = property.first;
const gd::PropertyDescriptor& propertyDescriptor = property.second;
if (propertyDescriptor.GetType() == "Behavior") {
requiredBehaviors.push_back(propertyDescriptor.GetExtraInfo()[0]);
const auto& extraInfos = propertyDescriptor.GetExtraInfo();
if (extraInfos.size() > 0) {
requiredBehaviors.push_back(extraInfos[0]);
}
}
}
return requiredBehaviors;

View File

@@ -12,6 +12,7 @@
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/String.h"
#include "GDCore/Project/QuickCustomization.h"
namespace gd {
class Behavior;
class BehaviorsSharedData;
@@ -41,10 +42,10 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
const gd::String& className_,
std::shared_ptr<gd::Behavior> instance,
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance);
/**
* \brief Construct a behavior metadata, without "blueprint" behavior.
*
*
* \note This is used by events based behaviors.
*/
BehaviorMetadata(
@@ -297,9 +298,18 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
BehaviorMetadata &SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
return *this;
}
/**
* \brief Return the associated gd::Behavior, handling behavior contents.
*
*
* \note Returns a dumb Behavior for events based behaviors as CustomBehavior
* are using EventBasedBehavior.
*/
@@ -317,7 +327,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
/**
* \brief Return the associated gd::BehaviorsSharedData, handling behavior
* shared data, if any (nullptr if none).
*
*
* \note Returns nullptr for events based behaviors as they don't declare
* shared data yet.
*/
@@ -374,6 +384,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
mutable std::vector<gd::String> requiredBehaviors;
bool isPrivate = false;
bool isHidden = false;
QuickCustomization::Visibility quickCustomizationVisibility;
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
std::shared_ptr<gd::Behavior> instance;

View File

@@ -38,12 +38,12 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
const gd::String& description,
const gd::String& supplementaryInformation,
bool parameterIsOptional) {
gd::ParameterMetadata info;
info.SetType(type);
info.description = description;
info.codeOnly = false;
info.SetOptional(parameterIsOptional);
info.SetExtraInfo(
parameters.AddNewParameter("")
.SetType(type)
.SetDescription(description)
.SetCodeOnly(false)
.SetOptional(parameterIsOptional)
.SetExtraInfo(
// For objects/behavior, the supplementary information
// parameter is an object/behavior type...
((gd::ParameterMetadata::IsObject(type) ||
@@ -59,22 +59,16 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
// TODO: Assert against supplementaryInformation === "emsc" (when running with
// Emscripten), and warn about a missing argument when calling addParameter.
parameters.push_back(info);
return *this;
}
gd::ExpressionMetadata& ExpressionMetadata::AddCodeOnlyParameter(
const gd::String& type, const gd::String& supplementaryInformation) {
gd::ParameterMetadata info;
info.SetType(type);
info.codeOnly = true;
info.SetExtraInfo(supplementaryInformation);
parameters.push_back(info);
gd::ExpressionMetadata &ExpressionMetadata::AddCodeOnlyParameter(
const gd::String &type, const gd::String &supplementaryInformation) {
parameters.AddNewParameter("").SetType(type).SetCodeOnly().SetExtraInfo(
supplementaryInformation);
return *this;
}
gd::ExpressionMetadata& ExpressionMetadata::SetRequiresBaseObjectCapability(
const gd::String& capability) {
requiredBaseObjectCapability = capability;

View File

@@ -193,8 +193,9 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
* \see AddParameter
*/
ExpressionMetadata &SetDefaultValue(const gd::String &defaultValue) override {
if (!parameters.empty())
parameters.back().SetDefaultValue(defaultValue);
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetDefaultValue(defaultValue);
}
return *this;
};
@@ -206,8 +207,9 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
*/
ExpressionMetadata &
SetParameterLongDescription(const gd::String &longDescription) override {
if (!parameters.empty())
parameters.back().SetLongDescription(longDescription);
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetLongDescription(longDescription);
}
return *this;
};
@@ -220,7 +222,9 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
*/
ExpressionMetadata &SetParameterExtraInfo(
const gd::String &extraInfo) override {
if (!parameters.empty()) parameters.back().SetExtraInfo(extraInfo);
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetExtraInfo(extraInfo);
}
return *this;
}
@@ -248,19 +252,16 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
const gd::String& GetGroup() const { return group; }
const gd::String& GetSmallIconFilename() const { return smallIconFilename; }
const gd::ParameterMetadata& GetParameter(std::size_t id) const {
return parameters[id];
return parameters.GetParameter(id);
};
gd::ParameterMetadata& GetParameter(std::size_t id) {
return parameters[id];
return parameters.GetParameter(id);
};
std::size_t GetParametersCount() const { return parameters.size(); };
const std::vector<gd::ParameterMetadata>& GetParameters() const {
std::size_t GetParametersCount() const { return parameters.GetParametersCount(); };
const gd::ParameterMetadataContainer& GetParameters() const {
return parameters;
};
std::vector<gd::ParameterMetadata> parameters;
/**
* \brief Set the function name which will be used when generating the code.
* \param functionName the name of the function to call
@@ -368,6 +369,8 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
bool isPrivate;
gd::String requiredBaseObjectCapability;
gd::String relevantContext;
gd::ParameterMetadataContainer parameters;
};
} // namespace gd

View File

@@ -9,6 +9,7 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/ParameterMetadataContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/Log.h"
@@ -77,7 +78,7 @@ InstructionMetadata& InstructionMetadata::AddParameter(
// TODO: Assert against supplementaryInformation === "emsc" (when running with
// Emscripten), and warn about a missing argument when calling addParameter.
parameters.push_back(info);
parameters.AddParameter(info);
return *this;
}
@@ -88,7 +89,7 @@ InstructionMetadata& InstructionMetadata::AddCodeOnlyParameter(
info.codeOnly = true;
info.SetExtraInfo(supplementaryInformation);
parameters.push_back(info);
parameters.AddParameter(info);
return *this;
}
@@ -102,7 +103,7 @@ InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters(
AddParameter(
"yesorno",
options.description.empty() ? _("New value") : options.description);
size_t valueParamIndex = parameters.size() - 1;
size_t valueParamIndex = parameters.GetParametersCount() - 1;
if (isObjectInstruction || isBehaviorInstruction) {
gd::String templateSentence = _("Set _PARAM0_ as <subject>: <value>");
@@ -127,8 +128,8 @@ InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters(
options.description.empty() ? _("Value") : options.description,
options.typeExtraInfo);
size_t operatorParamIndex = parameters.size() - 2;
size_t valueParamIndex = parameters.size() - 1;
size_t operatorParamIndex = parameters.GetParametersCount() - 2;
size_t valueParamIndex = parameters.GetParametersCount() - 1;
if (isObjectInstruction || isBehaviorInstruction) {
gd::String templateSentence = _("Change <subject> of _PARAM0_: <operator> <value>");
@@ -181,8 +182,8 @@ InstructionMetadata::UseStandardRelationalOperatorParameters(
AddParameter(type,
options.description.empty() ? _("Value to compare") : options.description,
options.typeExtraInfo);
size_t operatorParamIndex = parameters.size() - 2;
size_t valueParamIndex = parameters.size() - 1;
size_t operatorParamIndex = parameters.GetParametersCount() - 2;
size_t valueParamIndex = parameters.GetParametersCount() - 1;
if (isObjectInstruction || isBehaviorInstruction) {
gd::String templateSentence = _("<subject> of _PARAM0_ <operator> <value>");

View File

@@ -14,6 +14,7 @@
#include <memory>
#include "GDCore/Events/Instruction.h"
#include "GDCore/Project/ParameterMetadataContainer.h"
#include "GDCore/String.h"
#include "ParameterMetadata.h"
#include "ParameterOptions.h"
@@ -61,12 +62,12 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
const gd::String &GetDescription() const { return description; }
const gd::String &GetSentence() const { return sentence; }
const gd::String &GetGroup() const { return group; }
ParameterMetadata &GetParameter(size_t i) { return parameters[i]; }
ParameterMetadata &GetParameter(size_t i) { return parameters.GetParameter(i); }
const ParameterMetadata &GetParameter(size_t i) const {
return parameters[i];
return parameters.GetParameter(i);
}
size_t GetParametersCount() const { return parameters.size(); }
const std::vector<ParameterMetadata> &GetParameters() const {
size_t GetParametersCount() const { return parameters.GetParametersCount(); }
const ParameterMetadataContainer &GetParameters() const {
return parameters;
}
const gd::String &GetIconFilename() const { return iconFilename; }
@@ -256,7 +257,9 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
* \see AddParameter
*/
InstructionMetadata &SetDefaultValue(const gd::String &defaultValue_) override {
if (!parameters.empty()) parameters.back().SetDefaultValue(defaultValue_);
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetDefaultValue(defaultValue_);
}
return *this;
};
@@ -268,8 +271,9 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
*/
InstructionMetadata &SetParameterLongDescription(
const gd::String &longDescription) override {
if (!parameters.empty())
parameters.back().SetLongDescription(longDescription);
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetLongDescription(longDescription);
}
return *this;
}
@@ -281,7 +285,9 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
* \see AddParameter
*/
InstructionMetadata &SetParameterExtraInfo(const gd::String &extraInfo) override {
if (!parameters.empty()) parameters.back().SetExtraInfo(extraInfo);
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetExtraInfo(extraInfo);
}
return *this;
}
@@ -560,7 +566,7 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
*/
InstructionMetadata &GetCodeExtraInformation() { return *this; }
std::vector<ParameterMetadata> parameters;
ParameterMetadataContainer parameters;
private:
gd::String fullname;

View File

@@ -454,11 +454,12 @@ const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
// TODO use a badMetadata instead of a nullptr?
const gd::ParameterMetadata* parameterMetadata = nullptr;
while (metadataParameterIndex <
metadata.parameters.size()) {
if (!metadata.parameters[metadataParameterIndex]
metadata.GetParameters().GetParametersCount()) {
if (!metadata.GetParameters().GetParameter(metadataParameterIndex)
.IsCodeOnly()) {
if (visibleParameterIndex == parameterIndex) {
parameterMetadata = &metadata.parameters[metadataParameterIndex];
parameterMetadata =
&metadata.GetParameters().GetParameter(metadataParameterIndex);
}
visibleParameterIndex++;
}

View File

@@ -4,8 +4,8 @@
* reserved. This project is released under the MIT License.
*/
#ifndef PARAMETER_METADATA_H
#define PARAMETER_METADATA_H
#pragma once
#include <map>
#include <memory>
@@ -29,6 +29,12 @@ class GD_CORE_API ParameterMetadata {
ParameterMetadata();
virtual ~ParameterMetadata(){};
/**
* \brief Return a pointer to a new ParameterMetadata constructed from
* this one.
*/
ParameterMetadata* Clone() const { return new ParameterMetadata(*this); };
/**
* \brief Return the metadata of the parameter type.
*/
@@ -248,5 +254,3 @@ class GD_CORE_API ParameterMetadata {
};
} // namespace gd
#endif // PARAMETER_METADATA_H

View File

@@ -9,6 +9,7 @@
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/ParameterMetadataContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "InstructionMetadata.h"
@@ -20,13 +21,13 @@ const ParameterMetadata ParameterMetadataTools::badParameterMetadata;
void ParameterMetadataTools::ParametersToObjectsContainer(
const gd::Project& project,
const std::vector<gd::ParameterMetadata>& parameters,
const ParameterMetadataContainer& parameters,
gd::ObjectsContainer& outputObjectsContainer) {
outputObjectsContainer.GetObjects().clear();
gd::String lastObjectName;
for (std::size_t i = 0; i < parameters.size(); ++i) {
const auto& parameter = parameters[i];
for (std::size_t i = 0; i < parameters.GetParametersCount(); ++i) {
const auto& parameter = parameters.GetParameter(i);
if (parameter.GetName().empty()) continue;
if (gd::ParameterMetadata::IsObject(parameter.GetType())) {
@@ -61,31 +62,34 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
}
void ParameterMetadataTools::ForEachParameterMatchingSearch(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
const std::vector<const ParameterMetadataContainer*>&
parametersVectorsList,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
const ParameterMetadataContainer* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName().FindCaseInsensitive(search) != gd::String::npos) cb(parameterMetadata);
for (const auto &parameterMetadata :
parametersVector->GetInternalVector()) {
if (parameterMetadata->GetName().FindCaseInsensitive(search) !=
gd::String::npos)
cb(*parameterMetadata);
}
}
}
bool ParameterMetadataTools::Has(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const gd::String& parameterName) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
const ParameterMetadataContainer* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName() == parameterName) return true;
for (const auto& parameterMetadata: parametersVector->GetInternalVector()) {
if (parameterMetadata->GetName() == parameterName) return true;
}
}
@@ -93,16 +97,18 @@ bool ParameterMetadataTools::Has(
}
const gd::ParameterMetadata& ParameterMetadataTools::Get(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
const std::vector<const ParameterMetadataContainer*>&
parametersVectorsList,
const gd::String& parameterName) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
const ParameterMetadataContainer* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName() == parameterName) return parameterMetadata;
for (const auto &parameterMetadata :
parametersVector->GetInternalVector()) {
if (parameterMetadata->GetName() == parameterName)
return *parameterMetadata;
}
}
@@ -111,7 +117,7 @@ const gd::ParameterMetadata& ParameterMetadataTools::Get(
void ParameterMetadataTools::IterateOverParameters(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
const ParameterMetadataContainer& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
const gd::String& lastObjectName)> fn) {
@@ -128,15 +134,17 @@ void ParameterMetadataTools::IterateOverParameters(
void ParameterMetadataTools::IterateOverParametersWithIndex(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
const ParameterMetadataContainer& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
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::Expression& parameterValue =
for (std::size_t pNb = 0; pNb < parametersMetadata.GetParametersCount();
++pNb) {
const gd::ParameterMetadata &parameterMetadata =
parametersMetadata.GetParameter(pNb);
const gd::Expression &parameterValue =
pNb < parameters.size() ? parameters[pNb].GetPlainString() : "";
const gd::Expression& parameterValueOrDefault =
parameterValue.GetPlainString().empty() && parameterMetadata.IsOptional()
@@ -179,10 +187,10 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
size_t parameterIndex = 0;
for (size_t metadataIndex = (isObjectFunction ? 1 : 0);
metadataIndex < metadata.parameters.size() &&
metadataIndex < metadata.GetParameters().GetParametersCount() &&
parameterIndex < node.parameters.size();
++metadataIndex) {
auto &parameterMetadata = metadata.parameters[metadataIndex];
auto &parameterMetadata = metadata.GetParameters().GetParameter(metadataIndex);
if (parameterMetadata.IsCodeOnly()) {
continue;
}
@@ -204,16 +212,17 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
}
size_t ParameterMetadataTools::GetObjectParameterIndexFor(
const std::vector<gd::ParameterMetadata>& parametersMetadata,
const ParameterMetadataContainer& parametersMetadata,
size_t parameterIndex) {
// 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.
for (std::size_t pNb = parameterIndex; pNb < parametersMetadata.size();
pNb--) {
if (gd::ParameterMetadata::IsObject(parametersMetadata[pNb].GetType())) {
for (std::size_t pNb = parameterIndex;
pNb < parametersMetadata.GetParametersCount(); pNb--) {
if (gd::ParameterMetadata::IsObject(
parametersMetadata.GetParameter(pNb).GetType())) {
return pNb;
}
}

View File

@@ -15,6 +15,7 @@ class ObjectsContainer;
class ObjectsContainersList;
class ParameterMetadata;
class Expression;
class ParameterMetadataContainer;
struct FunctionCallNode;
struct ExpressionNode;
} // namespace gd
@@ -24,20 +25,20 @@ class GD_CORE_API ParameterMetadataTools {
public:
static void ParametersToObjectsContainer(
const gd::Project& project,
const std::vector<gd::ParameterMetadata>& parameters,
const ParameterMetadataContainer& parameters,
gd::ObjectsContainer& outputObjectsContainer);
static void ForEachParameterMatchingSearch(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb);
static bool Has(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const gd::String& parameterName);
static const gd::ParameterMetadata& Get(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const gd::String& parameterName);
/**
@@ -47,7 +48,7 @@ class GD_CORE_API ParameterMetadataTools {
*/
static void IterateOverParameters(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
const ParameterMetadataContainer& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
const gd::String& lastObjectName)> fn);
@@ -59,7 +60,7 @@ class GD_CORE_API ParameterMetadataTools {
*/
static void IterateOverParametersWithIndex(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
const ParameterMetadataContainer& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
@@ -84,7 +85,7 @@ class GD_CORE_API ParameterMetadataTools {
* it's linked to.
*/
static size_t GetObjectParameterIndexFor(
const std::vector<gd::ParameterMetadata>& parametersMetadata,
const ParameterMetadataContainer& parametersMetadata,
size_t parameterIndex);
private:

View File

@@ -210,8 +210,8 @@ class GD_CORE_API ValueTypeMetadata {
parameterType == "scenevar";
} else if (type == "resource") {
return parameterType == "fontResource" ||
parameterType == "soundfile" ||
parameterType == "musicfile" ||
parameterType == "audioResource" ||
parameterType == "videoResource" ||
parameterType == "bitmapFontResource" ||
parameterType == "imageResource" ||
parameterType == "jsonResource" ||
@@ -219,7 +219,10 @@ class GD_CORE_API ValueTypeMetadata {
parameterType == "tilesetResource" ||
parameterType == "model3DResource" ||
parameterType == "atlasResource" ||
parameterType == "spineResource";
parameterType == "spineResource" ||
// Deprecated, old parameter types:
parameterType == "soundfile" ||
parameterType == "musicfile";
}
return false;
}

View File

@@ -72,12 +72,12 @@ public:
gd::String lastObjectParameter = "";
const gd::InstructionMetadata &instrInfos =
MetadataProvider::GetActionMetadata(platform, instruction.GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType()) ||
"number", instrInfos.parameters.GetParameter(pNb).GetType()) ||
ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = instruction.GetParameter(pNb).GetRootNode();
node->Visit(*this);
}

View File

@@ -86,9 +86,11 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
}
size_t parameterIndex = 0;
for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size()
&& parameterIndex < node.parameters.size(); ++metadataIndex) {
auto& parameterMetadata = metadata.parameters[metadataIndex];
for (size_t metadataIndex = (isObjectFunction ? 1 : 0);
metadataIndex < metadata.GetParameters().GetParametersCount() &&
parameterIndex < node.parameters.size();
++metadataIndex) {
auto& parameterMetadata = metadata.GetParameters().GetParameter(metadataIndex);
if (parameterMetadata.IsCodeOnly()) {
continue;
}
@@ -144,10 +146,10 @@ class GD_CORE_API IdentifierFinderEventWorker
platform, instruction.GetType())
: MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
// The parameter has the searched type...
if (instrInfos.parameters[pNb].GetType() == "identifier"
&& instrInfos.parameters[pNb].GetExtraInfo() == identifierType) {
if (instrInfos.parameters.GetParameter(pNb).GetType() == "identifier"
&& instrInfos.parameters.GetParameter(pNb).GetExtraInfo() == identifierType) {
//...remember the value of the parameter.
if (objectName.empty() || lastObjectParameter == objectName) {
results.insert(instruction.GetParameter(pNb).GetPlainString());
@@ -155,9 +157,9 @@ class GD_CORE_API IdentifierFinderEventWorker
}
// Search in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType()) ||
"number", instrInfos.parameters.GetParameter(pNb).GetType()) ||
ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = instruction.GetParameter(pNb).GetRootNode();
IdentifierFinderExpressionNodeWorker searcher(
@@ -170,7 +172,7 @@ class GD_CORE_API IdentifierFinderEventWorker
}
// Remember the value of the last "object" parameter.
else if (gd::ParameterMetadata::IsObject(
instrInfos.parameters[pNb].GetType())) {
instrInfos.parameters.GetParameter(pNb).GetType())) {
lastObjectParameter =
instruction.GetParameter(pNb).GetPlainString();
}

View File

@@ -305,14 +305,14 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
for (std::size_t aId = 0; aId < actions.size(); ++aId) {
const gd::InstructionMetadata& instrInfos =
MetadataProvider::GetActionMetadata(platform, actions[aId].GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
// Replace object's name in parameters
if (gd::ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType()) &&
if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) &&
actions[aId].GetParameter(pNb).GetPlainString() == oldName)
actions[aId].SetParameter(pNb, gd::Expression(newName));
// Replace object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType())) {
"number", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
@@ -322,7 +322,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
}
// Replace object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
@@ -357,14 +357,14 @@ bool EventsRefactorer::RenameObjectInConditions(
const gd::InstructionMetadata& instrInfos =
MetadataProvider::GetConditionMetadata(platform,
conditions[cId].GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
// Replace object's name in parameters
if (gd::ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType()) &&
if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) &&
conditions[cId].GetParameter(pNb).GetPlainString() == oldName)
conditions[cId].SetParameter(pNb, gd::Expression(newName));
// Replace object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType())) {
"number", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
@@ -374,7 +374,7 @@ bool EventsRefactorer::RenameObjectInConditions(
}
// Replace object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
@@ -485,16 +485,16 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
const gd::InstructionMetadata& instrInfos =
MetadataProvider::GetActionMetadata(platform, actions[aId].GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
// Find object's name in parameters
if (gd::ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType()) &&
if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) &&
actions[aId].GetParameter(pNb).GetPlainString() == name) {
deleteMe = true;
break;
}
// Find object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType())) {
"number", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
@@ -504,7 +504,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
}
// Find object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
@@ -543,16 +543,16 @@ bool EventsRefactorer::RemoveObjectInConditions(
const gd::InstructionMetadata& instrInfos =
MetadataProvider::GetConditionMetadata(platform,
conditions[cId].GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
// Find object's name in parameters
if (gd::ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType()) &&
if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) &&
conditions[cId].GetParameter(pNb).GetPlainString() == name) {
deleteMe = true;
break;
}
// Find object's name in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType())) {
"number", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
@@ -562,7 +562,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
}
// Find object's name in text expressions
else if (ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {

View File

@@ -93,9 +93,11 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
}
size_t parameterIndex = 0;
for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size()
&& parameterIndex < node.parameters.size(); ++metadataIndex) {
auto& parameterMetadata = metadata.parameters[metadataIndex];
for (size_t metadataIndex = (isObjectFunction ? 1 : 0);
metadataIndex < metadata.GetParameters().GetParametersCount() &&
parameterIndex < node.parameters.size();
++metadataIndex) {
auto& parameterMetadata = metadata.GetParameters().GetParameter(metadataIndex);
if (parameterMetadata.IsCodeOnly()) {
continue;
}
@@ -150,18 +152,18 @@ class GD_CORE_API VariableFinderEventWorker
platform, instruction.GetType())
: MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
// The parameter has the searched type...
if (instrInfos.parameters[pNb].GetType() == parameterType) {
if (instrInfos.parameters.GetParameter(pNb).GetType() == parameterType) {
//...remember the value of the parameter.
if (objectName.empty() || lastObjectParameter == objectName)
results.insert(instruction.GetParameter(pNb).GetPlainString());
}
// Search in expressions
else if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters[pNb].GetType()) ||
"number", instrInfos.parameters.GetParameter(pNb).GetType()) ||
ParameterMetadata::IsExpression(
"string", instrInfos.parameters[pNb].GetType())) {
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
auto node = instruction.GetParameter(pNb).GetRootNode();
VariableFinderExpressionNodeWorker searcher(
@@ -174,7 +176,7 @@ class GD_CORE_API VariableFinderEventWorker
}
// Remember the value of the last "object" parameter.
else if (gd::ParameterMetadata::IsObject(
instrInfos.parameters[pNb].GetType())) {
instrInfos.parameters.GetParameter(pNb).GetType())) {
lastObjectParameter =
instruction.GetParameter(pNb).GetPlainString();
}

View File

@@ -463,11 +463,15 @@ class GD_CORE_API ExpressionCompletionFinder
MetadataProvider::GetFunctionCallMetadata(
platform, objectsContainersList, *functionCall);
const gd::ParameterMetadata* parameterMetadata = nullptr;
while (metadataParameterIndex < metadata.parameters.size()) {
if (!metadata.parameters[metadataParameterIndex].IsCodeOnly()) {
const gd::ParameterMetadata *parameterMetadata = nullptr;
while (metadataParameterIndex <
metadata.GetParameters().GetParametersCount()) {
if (!metadata.GetParameters()
.GetParameter(metadataParameterIndex)
.IsCodeOnly()) {
if (visibleParameterIndex == parameterIndex) {
parameterMetadata = &metadata.parameters[metadataParameterIndex];
parameterMetadata =
&metadata.GetParameters().GetParameter(metadataParameterIndex);
}
visibleParameterIndex++;
}

View File

@@ -36,13 +36,15 @@ namespace {
* (by convention, 1 for object functions and 2 for behavior functions).
*/
size_t GetMinimumParametersNumber(
const std::vector<gd::ParameterMetadata>& parameters,
const gd::ParameterMetadataContainer& parameters,
size_t initialParameterIndex) {
size_t nb = 0;
for (std::size_t i = initialParameterIndex; i < parameters.size(); ++i) {
if (!parameters[i].IsOptional() && !parameters[i].codeOnly) nb++;
for (std::size_t i = initialParameterIndex;
i < parameters.GetParametersCount(); ++i) {
if (!parameters.GetParameter(i).IsOptional() &&
!parameters.GetParameter(i).IsCodeOnly())
nb++;
}
return nb;
}
@@ -51,13 +53,14 @@ size_t GetMinimumParametersNumber(
* (by convention, 1 for object functions and 2 for behavior functions).
*/
size_t GetMaximumParametersNumber(
const std::vector<gd::ParameterMetadata>& parameters,
const gd::ParameterMetadataContainer& parameters,
size_t initialParameterIndex) {
size_t nb = 0;
for (std::size_t i = initialParameterIndex; i < parameters.size(); ++i) {
if (!parameters[i].codeOnly) nb++;
for (std::size_t i = initialParameterIndex;
i < parameters.GetParametersCount(); ++i) {
if (!parameters.GetParameter(i).IsCodeOnly())
nb++;
}
return nb;
}
@@ -322,11 +325,11 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
// Validate parameters count
size_t minParametersCount = GetMinimumParametersNumber(
metadata.parameters,
metadata.GetParameters(),
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName));
size_t maxParametersCount = GetMaximumParametersNumber(
metadata.parameters,
metadata.GetParameters(),
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName));
if (function.parameters.size() < minParametersCount ||
@@ -366,11 +369,11 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
for (int parameterIndex = 0; parameterIndex < function.parameters.size();
parameterIndex++) {
auto& parameter = function.parameters[parameterIndex];
while (metadata.GetParameters()[metadataIndex].IsCodeOnly()) {
while (metadata.GetParameters().GetParameter(metadataIndex).IsCodeOnly()) {
// The sizes are already checked above.
metadataIndex++;
}
auto& parameterMetadata = metadata.GetParameters()[metadataIndex];
auto& parameterMetadata = metadata.GetParameters().GetParameter(metadataIndex);
if (!parameterMetadata.IsOptional() ||
dynamic_cast<EmptyNode*>(parameter.get()) == nullptr) {

View File

@@ -144,10 +144,10 @@ bool ExpressionsParameterMover::DoVisitInstruction(gd::Instruction& instruction,
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
for (std::size_t pNb = 0; pNb < metadata.parameters.size() &&
for (std::size_t pNb = 0; pNb < metadata.parameters.GetParametersCount() &&
pNb < instruction.GetParametersCount();
++pNb) {
const gd::String& type = metadata.parameters[pNb].GetType();
const gd::String& type = metadata.parameters.GetParameter(pNb).GetType();
const gd::Expression& expression = instruction.GetParameter(pNb);
auto node = expression.GetRootNode();

View File

@@ -151,7 +151,7 @@ bool ExpressionsRenamer::DoVisitInstruction(gd::Instruction& instruction,
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
for (std::size_t pNb = 0; pNb < metadata.parameters.size() &&
for (std::size_t pNb = 0; pNb < metadata.parameters.GetParametersCount() &&
pNb < instruction.GetParametersCount();
++pNb) {
const gd::Expression& expression = instruction.GetParameter(pNb);

View File

@@ -43,7 +43,7 @@ InstructionSentenceFormatter::GetAsFormattedText(
parse = false;
size_t firstParamPosition = gd::String::npos;
size_t firstParamIndex = gd::String::npos;
for (std::size_t i = 0; i < metadata.parameters.size(); ++i) {
for (std::size_t i = 0; i < metadata.parameters.GetParametersCount(); ++i) {
size_t paramPosition =
sentence.find("_PARAM" + gd::String::From(i) + "_");
if (paramPosition < firstParamPosition) {

View File

@@ -225,9 +225,7 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
size_t parameterIndex,
const gd::String& lastObjectName) {
const String& parameterValue = parameterExpression.GetPlainString();
if (parameterMetadata.GetType() ==
"police" || // Should be renamed fontResource
parameterMetadata.GetType() == "fontResource") {
if (parameterMetadata.GetType() == "fontResource") {
gd::String updatedParameterValue = parameterValue;
worker.ExposeFont(updatedParameterValue);
instruction.SetParameter(parameterIndex, updatedParameterValue);

View File

@@ -15,10 +15,10 @@ namespace gd {
void FunctionParameterBehaviorTypeRenamer::DoVisitEventsFunction(
gd::EventsFunction &eventsFunction) {
for (auto &&parameter : eventsFunction.GetParameters()) {
if (gd::ParameterMetadata::IsBehavior(parameter.GetType()) &&
parameter.GetExtraInfo() == oldBehaviorType) {
parameter.SetExtraInfo(newBehaviorType);
for (auto &&parameter : eventsFunction.GetParameters().GetInternalVector()) {
if (gd::ParameterMetadata::IsBehavior(parameter->GetType()) &&
parameter->GetExtraInfo() == oldBehaviorType) {
parameter->SetExtraInfo(newBehaviorType);
}
}
}

View File

@@ -15,10 +15,10 @@ namespace gd {
void FunctionParameterObjectTypeRenamer::DoVisitEventsFunction(
gd::EventsFunction &eventsFunction) {
for (auto &&parameter : eventsFunction.GetParameters()) {
if (gd::ParameterMetadata::IsObject(parameter.GetType()) &&
parameter.GetExtraInfo() == oldObjectType) {
parameter.SetExtraInfo(newObjectType);
for (auto &&parameter : eventsFunction.GetParameters().GetInternalVector()) {
if (gd::ParameterMetadata::IsObject(parameter->GetType()) &&
parameter->GetExtraInfo() == oldObjectType) {
parameter->SetExtraInfo(newObjectType);
}
}
}

View File

@@ -153,7 +153,7 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
extension.GetName(), eventsBasedEntity.GetName());
objectParameter.SetExtraInfo(objectFullType);
}
setter.GetParameters().push_back(objectParameter);
setter.GetParameters().AddParameter(objectParameter);
if (isBehavior) {
gd::ParameterMetadata behaviorParameter;
gd::String behaviorFullType =
@@ -163,7 +163,7 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
.SetName("Behavior")
.SetDescription("Behavior")
.SetExtraInfo(behaviorFullType);
setter.GetParameters().push_back(behaviorParameter);
setter.GetParameters().AddParameter(behaviorParameter);
}
gd::ParameterMetadata valueParameter;
valueParameter.SetType("yesorno")
@@ -171,7 +171,7 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
.SetDescription(capitalizedName)
.SetOptional(true)
.SetDefaultValue("yes");
setter.GetParameters().push_back(valueParameter);
setter.GetParameters().AddParameter(valueParameter);
} else {
setter.SetFunctionType(gd::EventsFunction::ActionWithOperator);
setter.SetGetterName(getterName);

View File

@@ -102,17 +102,17 @@ void WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters(
for (auto &eventsFunction :
eventsBasedBehavior.GetEventsFunctions().GetInternalVector()) {
auto &parameters = eventsFunction->GetParameters();
while (parameters.size() < 2) {
while (parameters.GetParametersCount() < 2) {
gd::ParameterMetadata newParameter;
parameters.push_back(newParameter);
parameters.AddParameter(newParameter);
}
parameters[0]
parameters.GetParameter(0)
.SetType("object")
.SetName(behaviorObjectParameterName)
.SetDescription("Object")
.SetExtraInfo(eventsBasedBehavior.GetObjectType());
parameters[1]
parameters.GetParameter(1)
.SetType("behavior")
.SetName("Behavior")
.SetDescription("Behavior")
@@ -127,12 +127,12 @@ void WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
for (auto &eventsFunction :
eventsBasedObject.GetEventsFunctions().GetInternalVector()) {
auto &parameters = eventsFunction->GetParameters();
while (parameters.size() < 1) {
while (parameters.GetParametersCount() < 1) {
gd::ParameterMetadata newParameter;
parameters.push_back(newParameter);
parameters.AddParameter(newParameter);
}
parameters[0]
parameters.GetParameter(0)
.SetType("object")
.SetName(parentObjectParameterName)
.SetDescription("Object")

View File

@@ -9,6 +9,7 @@
#include <map>
#include <memory>
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Project/QuickCustomization.h"
#include "GDCore/String.h"
namespace gd {
@@ -31,10 +32,10 @@ namespace gd {
*/
class GD_CORE_API BehaviorConfigurationContainer {
public:
BehaviorConfigurationContainer() : folded(false){};
BehaviorConfigurationContainer() : folded(false), quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
BehaviorConfigurationContainer(const gd::String& name_,
const gd::String& type_)
: name(name_), type(type_), folded(false){};
: name(name_), type(type_), folded(false), quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
virtual ~BehaviorConfigurationContainer();
virtual BehaviorConfigurationContainer* Clone() const { return new BehaviorConfigurationContainer(*this); }
@@ -114,6 +115,13 @@ class GD_CORE_API BehaviorConfigurationContainer {
*/
bool IsFolded() const { return folded; }
void SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
protected:
/**
@@ -160,6 +168,7 @@ protected:
gd::SerializerElement content; // Storage for the behavior properties
bool folded;
QuickCustomization::Visibility quickCustomizationVisibility;
};
} // namespace gd

View File

@@ -26,7 +26,7 @@ void CustomConfigurationHelper::InitializeContent(
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
propertyType == "Resource") {
element.SetStringValue(property->GetValue());
} else if (propertyType == "Number") {
element.SetDoubleValue(property->GetValue().To<double>());
@@ -39,21 +39,21 @@ void CustomConfigurationHelper::InitializeContent(
std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetProperties(
const gd::PropertiesContainer &properties,
const gd::SerializerElement &configurationContent) {
auto behaviorProperties = std::map<gd::String, gd::PropertyDescriptor>();
auto objectProperties = std::map<gd::String, gd::PropertyDescriptor>();
for (auto &property : properties.GetInternalVector()) {
const auto &propertyName = property->GetName();
const auto &propertyType = property->GetType();
// Copy the property
behaviorProperties[propertyName] = *property;
objectProperties[propertyName] = *property;
auto &newProperty = behaviorProperties[propertyName];
auto &newProperty = objectProperties[propertyName];
if (configurationContent.HasChild(propertyName)) {
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
propertyType == "Resource") {
newProperty.SetValue(
configurationContent.GetChild(propertyName).GetStringValue());
} else if (propertyType == "Number") {
@@ -71,7 +71,7 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
}
}
return behaviorProperties;
return objectProperties;
}
bool CustomConfigurationHelper::UpdateProperty(
@@ -89,7 +89,7 @@ bool CustomConfigurationHelper::UpdateProperty(
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
propertyType == "Resource") {
element.SetStringValue(newValue);
} else if (propertyType == "Number") {
element.SetDoubleValue(newValue.To<double>());

View File

@@ -21,6 +21,9 @@ void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& object
project = objectConfiguration.project;
objectContent = objectConfiguration.objectContent;
animations = objectConfiguration.animations;
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
objectConfiguration
.isMarkedAsOverridingEventsBasedObjectChildrenConfiguration;
// There is no default copy for a map of unique_ptr like childObjectConfigurations.
childObjectConfigurations.clear();
@@ -42,6 +45,26 @@ const gd::EventsBasedObject* CustomObjectConfiguration::GetEventsBasedObject() c
return &project->GetEventsBasedObject(GetType());
}
bool CustomObjectConfiguration::
IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const {
const auto *eventsBasedObject = GetEventsBasedObject();
if (!eventsBasedObject) {
// True is safer because nothing will be lost when serializing.
return true;
}
return eventsBasedObject->GetInitialInstances().GetInstancesCount() == 0;
}
bool CustomObjectConfiguration::
IsOverridingEventsBasedObjectChildrenConfiguration() const {
return isMarkedAsOverridingEventsBasedObjectChildrenConfiguration ||
IsForcedToOverrideEventsBasedObjectChildrenConfiguration();
}
void CustomObjectConfiguration::ClearChildrenConfiguration() {
childObjectConfigurations.clear();
}
gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(const gd::String &objectName) {
const auto *eventsBasedObject = GetEventsBasedObject();
if (!eventsBasedObject) {
@@ -55,6 +78,18 @@ gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(
}
auto &childObject = eventsBasedObject->GetObjects().GetObject(objectName);
if (!IsOverridingEventsBasedObjectChildrenConfiguration()) {
// It should be fine because the editor doesn't allow to edit values when
// the default values from the events-based object is used.
//
// Resource refactor operations may modify it but they will do the same
// thing on the custom object as on the event-based object children so it
// shouldn't have any side effect.
return const_cast<gd::ObjectConfiguration &>(
childObject.GetConfiguration());
}
auto configurationPosition = childObjectConfigurations.find(objectName);
if (configurationPosition == childObjectConfigurations.end()) {
childObjectConfigurations.insert(std::make_pair(
@@ -67,7 +102,7 @@ gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(
auto &configuration = pair.second;
return *configuration;
}
}
}
std::map<gd::String, gd::PropertyDescriptor> CustomObjectConfiguration::GetProperties() const {
auto objectProperties = std::map<gd::String, gd::PropertyDescriptor>();
@@ -128,26 +163,14 @@ void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const
animations.SerializeTo(animatableElement);
}
auto &childrenContentElement = element.AddChild("childrenContent");
for (auto &pair : childObjectConfigurations) {
auto &childName = pair.first;
auto &childConfiguration = pair.second;
auto &childElement = childrenContentElement.AddChild(childName);
childConfiguration->SerializeTo(childElement);
}
const auto *eventsBasedObject = GetEventsBasedObject();
if (eventsBasedObject) {
eventsBasedObject->GetInitialInstances().SerializeTo(
element.AddChild("instances"));
eventsBasedObject->GetLayers().SerializeLayersTo(
element.AddChild("layers"));
element.SetIntAttribute("areaMinX", eventsBasedObject->GetAreaMinX());
element.SetIntAttribute("areaMinY", eventsBasedObject->GetAreaMinY());
element.SetIntAttribute("areaMinZ", eventsBasedObject->GetAreaMinZ());
element.SetIntAttribute("areaMaxX", eventsBasedObject->GetAreaMaxX());
element.SetIntAttribute("areaMaxY", eventsBasedObject->GetAreaMaxY());
element.SetIntAttribute("areaMaxZ", eventsBasedObject->GetAreaMaxZ());
if (IsOverridingEventsBasedObjectChildrenConfiguration()) {
auto &childrenContentElement = element.AddChild("childrenContent");
for (auto &pair : childObjectConfigurations) {
auto &childName = pair.first;
auto &childConfiguration = pair.second;
auto &childElement = childrenContentElement.AddChild(childName);
childConfiguration->SerializeTo(childElement);
}
}
}
void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
@@ -159,12 +182,16 @@ void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
animations.UnserializeFrom(animatableElement);
}
auto &childrenContentElement = element.GetChild("childrenContent");
for (auto &pair : childrenContentElement.GetAllChildren()) {
auto &childName = pair.first;
auto &childElement = pair.second;
auto &childConfiguration = GetChildObjectConfiguration(childName);
childConfiguration.UnserializeFrom(project, *childElement);
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
element.HasChild("childrenContent");
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
auto &childrenContentElement = element.GetChild("childrenContent");
for (auto &pair : childrenContentElement.GetAllChildren()) {
auto &childName = pair.first;
auto &childElement = pair.second;
auto &childConfiguration = GetChildObjectConfiguration(childName);
childConfiguration.UnserializeFrom(project, *childElement);
}
}
}
@@ -245,3 +272,16 @@ const SpriteAnimationList& CustomObjectConfiguration::GetAnimations() const {
SpriteAnimationList& CustomObjectConfiguration::GetAnimations() {
return animations;
}
const gd::CustomObjectConfiguration::EdgeAnchor
CustomObjectConfiguration::GetEdgeAnchorFromString(const gd::String &value) {
return (value == _("Window left") || value == _("Window top"))
? gd::CustomObjectConfiguration::EdgeAnchor::MinEdge
: (value == _("Window right") || value == _("Window bottom"))
? gd::CustomObjectConfiguration::EdgeAnchor::MaxEdge
: value == _("Proportional")
? gd::CustomObjectConfiguration::EdgeAnchor::Proportional
: value == _("Window center")
? gd::CustomObjectConfiguration::EdgeAnchor::Center
: gd::CustomObjectConfiguration::EdgeAnchor::NoAnchor;
}

View File

@@ -30,7 +30,7 @@ namespace gd {
class CustomObjectConfiguration : public gd::ObjectConfiguration {
public:
CustomObjectConfiguration(const Project& project_, const String& type_)
: project(&project_) {
: project(&project_), isMarkedAsOverridingEventsBasedObjectChildrenConfiguration(false) {
SetType(type_);
}
std::unique_ptr<gd::ObjectConfiguration> Clone() const override;
@@ -65,7 +65,22 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
void ExposeResources(gd::ArbitraryResourceWorker& worker) override;
gd::ObjectConfiguration &GetChildObjectConfiguration(const gd::String& objectName);
bool IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const;
bool IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() const {
return isMarkedAsOverridingEventsBasedObjectChildrenConfiguration;
}
void SetMarkedAsOverridingEventsBasedObjectChildrenConfiguration(
bool isOverridingEventsBasedObjectChildrenConfiguration_) {
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
isOverridingEventsBasedObjectChildrenConfiguration_;
}
void ClearChildrenConfiguration();
gd::ObjectConfiguration &
GetChildObjectConfiguration(const gd::String &objectName);
std::size_t GetAnimationsCount() const override;
@@ -83,6 +98,17 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
*/
SpriteAnimationList& GetAnimations();
enum EdgeAnchor {
NoAnchor = 0,
MinEdge = 1,
MaxEdge = 2,
Proportional = 3,
Center = 4,
};
static const gd::CustomObjectConfiguration::EdgeAnchor
GetEdgeAnchorFromString(const gd::String &value);
protected:
void DoSerializeTo(SerializerElement& element) const override;
void DoUnserializeFrom(Project& project, const SerializerElement& element) override;
@@ -90,10 +116,14 @@ protected:
private:
const gd::EventsBasedObject* GetEventsBasedObject() const;
bool IsOverridingEventsBasedObjectChildrenConfiguration() const;
const Project* project; ///< The project is used to get the
///< EventBasedObject from the fullType.
gd::SerializerElement objectContent;
std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration;
mutable std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;
static gd::ObjectConfiguration badObjectConfiguration;

View File

@@ -21,8 +21,8 @@ namespace gd {
*/
class GD_CORE_API Effect {
public:
Effect(){};
virtual ~Effect(){};
Effect() : folded(false) {};
virtual ~Effect() {};
void SetName(const gd::String& name_) { name = name_; }
const gd::String& GetName() const { return name; }
@@ -32,6 +32,9 @@ class GD_CORE_API Effect {
}
const gd::String& GetEffectType() const { return effectType; }
void SetFolded(bool fold = true) { folded = fold; }
bool IsFolded() const { return folded; }
void SetDoubleParameter(const gd::String& name, double value) {
doubleParameters[name] = value;
}
@@ -85,6 +88,7 @@ class GD_CORE_API Effect {
void UnserializeFrom(const SerializerElement& element);
private:
bool folded;
gd::String name; ///< The name of the layer.
gd::String effectType; ///< The name of the effect to apply.
std::map<gd::String, double> doubleParameters; ///< Values of parameters being doubles, keyed by names.

View File

@@ -4,6 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#include "EventsBasedBehavior.h"
#include "EventsFunctionsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/MakeUnique.h"
@@ -12,9 +13,10 @@ namespace gd {
EventsBasedBehavior::EventsBasedBehavior()
: AbstractEventsBasedEntity(
"MyBehavior",
gd::EventsFunctionsContainer::FunctionOwner::Behavior),
sharedPropertyDescriptors(gd::EventsFunctionsContainer::FunctionOwner::Behavior) {}
"MyBehavior", gd::EventsFunctionsContainer::FunctionOwner::Behavior),
sharedPropertyDescriptors(
gd::EventsFunctionsContainer::FunctionOwner::Behavior),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
void EventsBasedBehavior::SerializeTo(SerializerElement& element) const {
AbstractEventsBasedEntity::SerializeTo(element);
@@ -24,6 +26,13 @@ void EventsBasedBehavior::SerializeTo(SerializerElement& element) const {
}
sharedPropertyDescriptors.SerializeElementsTo(
"propertyDescriptor", element.AddChild("sharedPropertyDescriptors"));
if (quickCustomizationVisibility != QuickCustomization::Visibility::Default) {
element.SetStringAttribute(
"quickCustomizationVisibility",
quickCustomizationVisibility == QuickCustomization::Visibility::Visible
? "visible"
: "hidden");
}
}
void EventsBasedBehavior::UnserializeFrom(gd::Project& project,
@@ -33,6 +42,14 @@ void EventsBasedBehavior::UnserializeFrom(gd::Project& project,
isPrivate = element.GetBoolAttribute("private");
sharedPropertyDescriptors.UnserializeElementsFrom(
"propertyDescriptor", element.GetChild("sharedPropertyDescriptors"));
if (element.HasChild("quickCustomizationVisibility")) {
quickCustomizationVisibility =
element.GetStringAttribute("quickCustomizationVisibility") == "visible"
? QuickCustomization::Visibility::Visible
: QuickCustomization::Visibility::Hidden;
} else {
quickCustomizationVisibility = QuickCustomization::Visibility::Default;
}
}
} // namespace gd

View File

@@ -11,6 +11,7 @@
#include "GDCore/Project/NamedPropertyDescriptor.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/Project/QuickCustomization.h"
#include "GDCore/String.h"
namespace gd {
class SerializerElement;
@@ -88,6 +89,15 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
EventsBasedBehavior& SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
return *this;
}
/**
* \brief Return a reference to the list of shared properties.
*/
@@ -141,6 +151,7 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
gd::String objectType;
bool isPrivate = false;
gd::PropertiesContainer sharedPropertyDescriptors;
QuickCustomization::Visibility quickCustomizationVisibility;
};
} // namespace gd

View File

@@ -16,6 +16,8 @@ EventsBasedObject::EventsBasedObject()
isRenderedIn3D(false),
isAnimatable(false),
isTextContainer(false),
isInnerAreaFollowingParentSize(false),
isUsingLegacyInstancesRenderer(false),
areaMinX(0),
areaMinY(0),
areaMinZ(0),
@@ -37,6 +39,10 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
if (isTextContainer) {
element.SetBoolAttribute("isTextContainer", true);
}
if (isInnerAreaFollowingParentSize) {
element.SetBoolAttribute("isInnerAreaFollowingParentSize", true);
}
element.SetBoolAttribute("isUsingLegacyInstancesRenderer", isUsingLegacyInstancesRenderer);
element.SetIntAttribute("areaMinX", areaMinX);
element.SetIntAttribute("areaMinY", areaMinY);
element.SetIntAttribute("areaMinZ", areaMinZ);
@@ -59,6 +65,8 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project,
isRenderedIn3D = element.GetBoolAttribute("is3D", false);
isAnimatable = element.GetBoolAttribute("isAnimatable", false);
isTextContainer = element.GetBoolAttribute("isTextContainer", false);
isInnerAreaFollowingParentSize =
element.GetBoolAttribute("isInnerAreaFollowingParentSize", false);
areaMinX = element.GetIntAttribute("areaMinX", 0);
areaMinY = element.GetIntAttribute("areaMinY", 0);
areaMinZ = element.GetIntAttribute("areaMinZ", 0);
@@ -82,6 +90,15 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project,
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
if (element.HasAttribute("isUsingLegacyInstancesRenderer")) {
isUsingLegacyInstancesRenderer =
element.GetBoolAttribute("isUsingLegacyInstancesRenderer", false);
}
else {
// Compatibility with GD <= 5.4.212
isUsingLegacyInstancesRenderer = initialInstances.GetInstancesCount() == 0;
// end of compatibility code
}
}
} // namespace gd

View File

@@ -101,11 +101,53 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
/**
* \brief Declare a TextContainer capability.
*/
EventsBasedObject& MarkAsTextContainer(bool isTextContainer_) {
EventsBasedObject &MarkAsTextContainer(bool isTextContainer_) {
isTextContainer = isTextContainer_;
return *this;
}
/**
* \brief Declare that the parent scale will always be 1 and children will
* adapt there size. This is removing the ScalableCapability.
*/
EventsBasedObject &
MarkAsInnerAreaFollowingParentSize(bool isInnerAreaExpandingWithParent_) {
isInnerAreaFollowingParentSize = isInnerAreaExpandingWithParent_;
return *this;
}
/**
* \brief Return true if objects handle size changes on their own and
* don't have the ScalableCapability.
*
* When the parent dimensions change:
* - if `false`, the object is stretch proportionally while children local
* positions stay the same.
* - if `true`, the children local positions need to be adapted by events
* to follow their parent size.
*/
bool IsInnerAreaFollowingParentSize() const {
return isInnerAreaFollowingParentSize;
}
/**
* \brief Declare that custom object are rendered using their child-objects
* instead of their child-instances.
*/
EventsBasedObject &
MakAsUsingLegacyInstancesRenderer(bool isUsingLegacyInstancesRenderer_) {
isUsingLegacyInstancesRenderer = isUsingLegacyInstancesRenderer_;
return *this;
}
/**
* \brief Return true if custom object are rendered using their child-objects
* instead of their child-instances.
*/
bool IsUsingLegacyInstancesRenderer() const {
return isUsingLegacyInstancesRenderer;
}
/**
* \brief Return true if the object needs a TextContainer capability.
*/
@@ -279,6 +321,8 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
bool isRenderedIn3D;
bool isAnimatable;
bool isTextContainer;
bool isInnerAreaFollowingParentSize;
bool isUsingLegacyInstancesRenderer;
gd::InitialInstancesContainer initialInstances;
gd::LayersContainer layers;
gd::ObjectsContainer objectsContainer;

View File

@@ -16,41 +16,41 @@ EventsFunction::EventsFunction() : functionType(Action) {
expressionType.SetName("expression");
}
const std::vector<gd::ParameterMetadata>& EventsFunction::GetParametersForEvents(
const gd::EventsFunctionsContainer& functionsContainer) const {
const gd::ParameterMetadataContainer &EventsFunction::GetParametersForEvents(
const gd::EventsFunctionsContainer &functionsContainer) const {
if (functionType != FunctionType::ActionWithOperator) {
// For most function types, the parameters are specified in the function.
return parameters;
}
// For ActionWithOperator, the parameters are auto generated.
actionWithOperationParameters.clear();
actionWithOperationParameters.ClearParameters();
if (!functionsContainer.HasEventsFunctionNamed(getterName)) {
return actionWithOperationParameters;
}
const auto& expression = functionsContainer.GetEventsFunction(getterName);
const auto& expressionParameters = expression.parameters;
const auto &expression = functionsContainer.GetEventsFunction(getterName);
const auto &expressionParameters = expression.parameters;
const auto functionsSource = functionsContainer.GetOwner();
const int expressionValueParameterIndex =
functionsSource == gd::EventsFunctionsContainer::FunctionOwner::Behavior ?
2 :
functionsSource == gd::EventsFunctionsContainer::FunctionOwner::Object ?
1 :
0;
for (size_t i = 0;
i < expressionValueParameterIndex && i < expressionParameters.size();
i++)
{
actionWithOperationParameters.push_back(expressionParameters[i]);
functionsSource == gd::EventsFunctionsContainer::FunctionOwner::Behavior
? 2
: functionsSource == gd::EventsFunctionsContainer::FunctionOwner::Object
? 1
: 0;
for (size_t i = 0; i < expressionValueParameterIndex &&
i < expressionParameters.GetParametersCount();
i++) {
actionWithOperationParameters.AddParameter(
expressionParameters.GetParameter(i));
}
gd::ParameterMetadata parameterMetadata;
parameterMetadata.SetName("Value").SetValueTypeMetadata(expression.expressionType);
actionWithOperationParameters.push_back(parameterMetadata);
parameterMetadata.SetName("Value").SetValueTypeMetadata(
expression.expressionType);
actionWithOperationParameters.AddParameter(parameterMetadata);
for (size_t i = expressionValueParameterIndex;
i < expressionParameters.size();
i++)
{
actionWithOperationParameters.push_back(expressionParameters[i]);
i < expressionParameters.GetParametersCount(); i++) {
actionWithOperationParameters.AddParameter(
expressionParameters.GetParameter(i));
}
return actionWithOperationParameters;
@@ -101,10 +101,7 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
expressionType.SerializeTo(element.AddChild("expressionType"));
}
gd::SerializerElement& parametersElement = element.AddChild("parameters");
parametersElement.ConsiderAsArrayOf("parameter");
for (const auto& parameter : parameters) {
parameter.SerializeTo(parametersElement.AddChild("parameter"));
}
parameters.SerializeParametersTo(parametersElement);
objectGroups.SerializeTo(element.AddChild("objectGroups"));
}
@@ -146,15 +143,9 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
else
functionType = Action;
const gd::SerializerElement& parametersElement =
const gd::SerializerElement &parametersElement =
element.GetChild("parameters");
parameters.clear();
parametersElement.ConsiderAsArrayOf("parameter");
for (std::size_t i = 0; i < parametersElement.GetChildrenCount(); ++i) {
ParameterMetadata parameter;
parameter.UnserializeFrom(parametersElement.GetChild(i));
parameters.push_back(parameter);
}
parameters.UnserializeParametersFrom(parametersElement);
objectGroups.UnserializeFrom(element.GetChild("objectGroups"));
}

View File

@@ -3,14 +3,13 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef GDCORE_EVENTSFUNCTION_H
#define GDCORE_EVENTSFUNCTION_H
#pragma once
#include <vector>
#include "GDCore/Events/EventsList.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ParameterMetadataContainer.h"
#include "GDCore/String.h"
#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
// TODO: In theory (for separation of concerns between Project and
@@ -241,7 +240,7 @@ class GD_CORE_API EventsFunction {
* to the generated function, like "runtimeScene" and "eventsFunctionContext".
* This should be transparent to the user.
*/
const std::vector<gd::ParameterMetadata>& GetParametersForEvents(
const gd::ParameterMetadataContainer& GetParametersForEvents(
const gd::EventsFunctionsContainer& functionsContainer) const;
/**
@@ -254,14 +253,14 @@ class GD_CORE_API EventsFunction {
* to the generated function, like "runtimeScene" and "eventsFunctionContext".
* This should be transparent to the user.
*/
const std::vector<gd::ParameterMetadata>& GetParameters() const {
const ParameterMetadataContainer& GetParameters() const {
return parameters;
};
/**
* \brief Return the parameters.
*/
std::vector<gd::ParameterMetadata>& GetParameters() { return parameters; };
ParameterMetadataContainer& GetParameters() { return parameters; };
/**
* \brief Return a reference to the object groups that can be used in the
@@ -300,14 +299,11 @@ class GD_CORE_API EventsFunction {
gd::ValueTypeMetadata expressionType;
gd::EventsList events;
FunctionType functionType;
std::vector<gd::ParameterMetadata> parameters;
mutable std::vector<gd::ParameterMetadata> actionWithOperationParameters;
ParameterMetadataContainer parameters;
mutable gd::ParameterMetadataContainer actionWithOperationParameters;
gd::ObjectGroupsContainer objectGroups;
bool isPrivate = false;
bool isAsync = false;
};
} // namespace gd
#endif // GDCORE_EVENTSFUNCTION_H
#endif

View File

@@ -96,19 +96,6 @@ class GD_CORE_API ExternalLayout {
gd::String associatedLayout;
};
/**
* \brief Functor testing ExternalLayout' name
*/
struct ExternalLayoutHasName
: public std::binary_function<std::unique_ptr<gd::ExternalLayout>,
gd::String,
bool> {
bool operator()(const std::unique_ptr<gd::ExternalLayout>& externalLayout,
gd::String name) const {
return externalLayout->GetName() == name;
}
};
} // namespace gd
#endif // GDCORE_EXTERNALLAYOUT_H

View File

@@ -27,7 +27,11 @@ InitialInstance::InitialInstance()
rotationX(0),
rotationY(0),
zOrder(0),
opacity(255),
layer(""),
flippedX(false),
flippedY(false),
flippedZ(false),
customSize(false),
customDepth(false),
width(0),
@@ -57,7 +61,11 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
SetHasCustomDepth(false);
}
SetZOrder(element.GetIntAttribute("zOrder", 0, "plan"));
SetOpacity(element.GetIntAttribute("opacity", 255));
SetLayer(element.GetStringAttribute("layer"));
SetFlippedX(element.GetBoolAttribute("flippedX", false));
SetFlippedY(element.GetBoolAttribute("flippedY", false));
SetFlippedZ(element.GetBoolAttribute("flippedZ", false));
SetLocked(element.GetBoolAttribute("locked", false));
SetSealed(element.GetBoolAttribute("sealed", false));
SetShouldKeepRatio(element.GetBoolAttribute("keepRatio", false));
@@ -113,6 +121,10 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
element.SetAttribute("y", GetY());
if (GetZ() != 0) element.SetAttribute("z", GetZ());
element.SetAttribute("zOrder", GetZOrder());
if (GetOpacity() != 255) element.SetAttribute("opacity", GetOpacity());
if (IsFlippedX()) element.SetAttribute("flippedX", IsFlippedX());
if (IsFlippedY()) element.SetAttribute("flippedY", IsFlippedY());
if (IsFlippedZ()) element.SetAttribute("flippedZ", IsFlippedZ());
element.SetAttribute("layer", GetLayer());
element.SetAttribute("angle", GetAngle());
if (GetRotationX() != 0) element.SetAttribute("rotationX", GetRotationX());
@@ -155,8 +167,8 @@ InitialInstance& InitialInstance::ResetPersistentUuid() {
std::map<gd::String, gd::PropertyDescriptor>
InitialInstance::GetCustomProperties(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer) {
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer) {
// Find an object
if (objectsContainer.HasObjectNamed(GetObjectName()))
return objectsContainer.GetObject(GetObjectName())
@@ -172,9 +184,10 @@ InitialInstance::GetCustomProperties(
}
bool InitialInstance::UpdateCustomProperty(
const gd::String &name, const gd::String &value,
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer) {
const gd::String& name,
const gd::String& value,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer) {
if (objectsContainer.HasObjectNamed(GetObjectName()))
return objectsContainer.GetObject(GetObjectName())
.GetConfiguration()

View File

@@ -29,7 +29,7 @@ class GD_CORE_API InitialInstance {
* \brief Create an initial instance pointing to no object, at position (0,0).
*/
InitialInstance();
virtual ~InitialInstance(){};
virtual ~InitialInstance() {};
/**
* Must return a pointer to a copy of the object. A such method is needed to
@@ -123,6 +123,46 @@ class GD_CORE_API InitialInstance {
*/
void SetZOrder(int zOrder_) { zOrder = zOrder_; }
/**
* \brief Get Opacity.
*/
int GetOpacity() const { return opacity; }
/**
* \brief Set the opacity of the instance.
*/
void SetOpacity(int opacity_) { opacity = opacity_; }
/**
* \brief Return true if the instance is flipped on X axis.
*/
bool IsFlippedX() const { return flippedX; }
/**
* \brief Set whether the instance is flipped on X axis.
*/
void SetFlippedX(bool flippedX_) { flippedX = flippedX_; }
/**
* \brief Return true if the instance is flipped on Y axis.
*/
bool IsFlippedY() const { return flippedY; }
/**
* \brief Set whether the instance is flipped on Y axis.
*/
void SetFlippedY(bool flippedY_) { flippedY = flippedY_; }
/**
* \brief Return true if the instance is flipped on Z axis.
*/
bool IsFlippedZ() const { return flippedZ; }
/**
* \brief Set whether the instance is flipped on Z axis.
*/
void SetFlippedZ(bool flippedZ_) { flippedZ = flippedZ_; }
/**
* \brief Get the layer the instance belongs to.
*/
@@ -134,8 +174,9 @@ class GD_CORE_API InitialInstance {
void SetLayer(const gd::String& layer_) { layer = layer_; }
/**
* \brief Return true if the instance has a width/height which is different from its
* object default width/height. This is independent from `HasCustomDepth`.
* \brief Return true if the instance has a width/height which is different
* from its object default width/height. This is independent from
* `HasCustomDepth`.
*
* \see gd::Object
*/
@@ -150,15 +191,13 @@ class GD_CORE_API InitialInstance {
bool HasCustomDepth() const { return customDepth; }
/**
* \brief Set whether the instance has a width/height which is different from its
* object default width/height or not.
* This is independent from `SetHasCustomDepth`.
* \brief Set whether the instance has a width/height which is different from
* its object default width/height or not. This is independent from
* `SetHasCustomDepth`.
*
* \see gd::Object
*/
void SetHasCustomSize(bool hasCustomSize_) {
customSize = hasCustomSize_;
}
void SetHasCustomSize(bool hasCustomSize_) { customSize = hasCustomSize_; }
/**
* \brief Set whether the instance has a depth which is different from its
@@ -264,18 +303,19 @@ class GD_CORE_API InitialInstance {
* \note Common properties ( name, position... ) do not need to be
* inserted in this map
*/
std::map<gd::String, gd::PropertyDescriptor>
GetCustomProperties(gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer);
std::map<gd::String, gd::PropertyDescriptor> GetCustomProperties(
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer);
/**
* \brief Update the property called \a name with the new \a value.
*
* \return false if the property could not be updated.
*/
bool UpdateCustomProperty(const gd::String &name, const gd::String &value,
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer);
bool UpdateCustomProperty(const gd::String& name,
const gd::String& value,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer);
/**
* \brief Get the value of a double property stored in the instance.
@@ -343,6 +383,10 @@ class GD_CORE_API InitialInstance {
double rotationX; ///< Instance angle on X axis (for a 3D object)
double rotationY; ///< Instance angle on Y axis (for a 3D object)
int zOrder; ///< Instance Z order (for a 2D object)
int opacity; ///< Instance opacity
bool flippedX; ///< True if the instance is flipped on X axis
bool flippedY; ///< True if the instance is flipped on Y axis
bool flippedZ; ///< True if the instance is flipped on Z axis
gd::String layer; ///< Instance layer
bool customSize; ///< True if object has a custom width and height
bool customDepth; ///< True if object has a custom depth
@@ -352,13 +396,13 @@ class GD_CORE_API InitialInstance {
gd::VariablesContainer initialVariables; ///< Instance specific variables
bool locked; ///< True if the instance is locked
bool sealed; ///< True if the instance is sealed
bool keepRatio; ///< True if the instance's dimensions
/// should keep the same ratio.
bool keepRatio; ///< True if the instance's dimensions
/// should keep the same ratio.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
/// useful for hot reloading.
static gd::String*
badStringPropertyValue; ///< Empty string returned by GetRawStringProperty
static gd::String* badStringPropertyValue; ///< Empty string returned by
///< GetRawStringProperty
};
} // namespace gd

View File

@@ -405,18 +405,6 @@ class GD_CORE_API Layout {
const gd::String& behaviorsType);
};
/**
* \brief Functor testing layout name.
* \see gd::Layout
*/
struct LayoutHasName
: public std::binary_function<std::unique_ptr<Layout>, gd::String, bool> {
bool operator()(const std::unique_ptr<Layout>& layout,
gd::String name) const {
return layout->GetName() == name;
}
};
/**
* \brief Get the names of all layers from the given layout
* that are invisible.

View File

@@ -19,13 +19,20 @@ namespace gd {
ObjectFolderOrObject ObjectFolderOrObject::badObjectFolderOrObject;
ObjectFolderOrObject::ObjectFolderOrObject()
: folderName("__NULL"), object(nullptr) {}
: folderName("__NULL"),
object(nullptr),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::String folderName_,
ObjectFolderOrObject* parent_)
: folderName(folderName_), parent(parent_), object(nullptr) {}
: folderName(folderName_),
parent(parent_),
object(nullptr),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::Object* object_,
ObjectFolderOrObject* parent_)
: object(object_), parent(parent_) {}
: object(object_),
parent(parent_),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
ObjectFolderOrObject::~ObjectFolderOrObject() {}
bool ObjectFolderOrObject::HasObjectNamed(const gd::String& name) {
@@ -66,7 +73,8 @@ ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(std::size_t index) {
if (index >= children.size()) return badObjectFolderOrObject;
return *children[index];
}
const ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(std::size_t index) const {
const ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(
std::size_t index) const {
if (index >= children.size()) return badObjectFolderOrObject;
return *children[index];
}
@@ -206,6 +214,14 @@ void ObjectFolderOrObject::SerializeTo(SerializerElement& element) const {
} else {
element.SetAttribute("objectName", GetObject().GetName());
}
if (quickCustomizationVisibility != QuickCustomization::Visibility::Default) {
element.SetStringAttribute(
"quickCustomizationVisibility",
quickCustomizationVisibility == QuickCustomization::Visibility::Visible
? "visible"
: "hidden");
}
}
void ObjectFolderOrObject::UnserializeFrom(
@@ -243,6 +259,15 @@ void ObjectFolderOrObject::UnserializeFrom(
object = nullptr;
}
}
if (element.HasChild("quickCustomizationVisibility")) {
quickCustomizationVisibility =
element.GetStringAttribute("quickCustomizationVisibility") == "visible"
? QuickCustomization::Visibility::Visible
: QuickCustomization::Visibility::Hidden;
} else {
quickCustomizationVisibility = QuickCustomization::Visibility::Default;
}
};
} // namespace gd

View File

@@ -10,6 +10,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Project/QuickCustomization.h"
namespace gd {
class Project;
@@ -166,6 +167,11 @@ class GD_CORE_API ObjectFolderOrObject {
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition);
QuickCustomization::Visibility GetQuickCustomizationVisibility() const { return quickCustomizationVisibility; }
void SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
}
/** \name Saving and loading
* Members functions related to saving and loading the objects of the class.
*/
@@ -188,6 +194,7 @@ class GD_CORE_API ObjectFolderOrObject {
gd::ObjectFolderOrObject*
parent; // nullptr if root folder, points to the parent folder otherwise.
QuickCustomization::Visibility quickCustomizationVisibility;
// Representing an object:
gd::Object* object; // nullptr if folderName is set.

View File

@@ -589,4 +589,13 @@ std::vector<gd::String> ObjectsContainersList::GetAnimationNamesOfObject(
return animationNames;
}
const gd::ObjectsContainer &
ObjectsContainersList::GetObjectsContainer(std::size_t index) const {
return *objectsContainers[index];
}
std::size_t ObjectsContainersList::GetObjectsContainersCount() const {
return objectsContainers.size();
}
} // namespace gd

View File

@@ -173,6 +173,16 @@ class GD_CORE_API ObjectsContainersList {
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const;
/**
* \brief Return a the objects container at position \a index.
*/
const gd::ObjectsContainer &GetObjectsContainer(std::size_t index) const;
/**
* \brief Return the number of objects containers.
*/
std::size_t GetObjectsContainersCount() const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
ObjectsContainersList(){};

View File

@@ -0,0 +1,148 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include <vector>
namespace gd {
class SerializerElement;
}
namespace gd {
/**
* \brief Used as a base class for classes that will own events-backed
* functions.
*
* \see gd::ParameterMetadata
* \ingroup PlatformDefinition
*/
class GD_CORE_API ParameterMetadataContainer
: private SerializableWithNameList<gd::ParameterMetadata> {
public:
ParameterMetadataContainer() {}
/** \name Events Functions management
*/
///@{
/**
* \brief Check if the function with the specified name exists.
*/
bool HasParameterNamed(const gd::String &name) const { return Has(name); }
/**
* \brief Get the function with the specified name.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
gd::ParameterMetadata &GetParameter(const gd::String &name) {
return Get(name);
}
/**
* \brief Get the function with the specified name.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
const gd::ParameterMetadata &GetParameter(const gd::String &name) const {
return Get(name);
}
/**
* \brief Get the function at the specified index in the list.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
gd::ParameterMetadata &GetParameter(std::size_t index) { return Get(index); }
/**
* \brief Get the function at the specified index in the list.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
const gd::ParameterMetadata &GetParameter(std::size_t index) const {
return Get(index);
}
/**
* \brief Return the number of functions.
*/
std::size_t GetParametersCount() const { return GetCount(); }
gd::ParameterMetadata &InsertNewParameter(const gd::String &name,
std::size_t position) {
return InsertNew(name, position);
}
gd::ParameterMetadata &InsertParameter(const gd::ParameterMetadata &object,
std::size_t position) {
return Insert(object, position);
}
gd::ParameterMetadata &AddNewParameter(const gd::String &name) {
return InsertNew(name, GetCount());
}
gd::ParameterMetadata &AddParameter(const gd::ParameterMetadata &object) {
return Insert(object, GetCount());
}
void RemoveParameter(const gd::String &name) { return Remove(name); }
void ClearParameters() { return Clear(); }
void MoveParameter(std::size_t oldIndex, std::size_t newIndex) {
return Move(oldIndex, newIndex);
};
std::size_t
GetParameterPosition(const gd::ParameterMetadata &parameterMetadata) {
return GetPosition(parameterMetadata);
};
/**
* \brief Provide a raw access to the vector containing the functions.
*/
const std::vector<std::unique_ptr<gd::ParameterMetadata>> &
GetInternalVector() const {
return elements;
};
/**
* \brief Provide a raw access to the vector containing the functions.
*/
std::vector<std::unique_ptr<gd::ParameterMetadata>> &GetInternalVector() {
return elements;
};
///@}
/** \name Serialization
*/
///@{
/**
* \brief Serialize events functions.
*/
void SerializeParametersTo(SerializerElement &element) const {
return SerializeElementsTo("parameters", element);
};
/**
* \brief Unserialize the events functions.
*/
void UnserializeParametersFrom(const SerializerElement &element) {
return UnserializeElementsFrom("parameters", element);
};
///@}
protected:
/**
* Initialize object using another object. Used by copy-ctor and assign-op.
* Don't forget to update me if members were changed!
*/
void Init(const gd::ParameterMetadataContainer &other) {
return SerializableWithNameList<gd::ParameterMetadata>::Init(other);
};
};
} // namespace gd

View File

@@ -264,15 +264,21 @@ bool Project::RemovePlatform(const gd::String& platformName) {
bool Project::HasLayoutNamed(const gd::String& name) const {
return (find_if(scenes.begin(),
scenes.end(),
bind2nd(gd::LayoutHasName(), name)) != scenes.end());
[&name](const std::unique_ptr<gd::Layout>& layout) {
return layout->GetName() == name;
}) != scenes.end());
}
gd::Layout& Project::GetLayout(const gd::String& name) {
return *(*find_if(
scenes.begin(), scenes.end(), bind2nd(gd::LayoutHasName(), name)));
scenes.begin(), scenes.end(), [&name](const std::unique_ptr<gd::Layout>& layout) {
return layout->GetName() == name;
}));
}
const gd::Layout& Project::GetLayout(const gd::String& name) const {
return *(*find_if(
scenes.begin(), scenes.end(), bind2nd(gd::LayoutHasName(), name)));
scenes.begin(), scenes.end(), [&name](const std::unique_ptr<gd::Layout>& layout) {
return layout->GetName() == name;
}));
}
gd::Layout& Project::GetLayout(std::size_t index) { return *scenes[index]; }
const gd::Layout& Project::GetLayout(std::size_t index) const {
@@ -317,7 +323,9 @@ gd::Layout& Project::InsertLayout(const gd::Layout& layout,
void Project::RemoveLayout(const gd::String& name) {
std::vector<std::unique_ptr<gd::Layout> >::iterator scene =
find_if(scenes.begin(), scenes.end(), bind2nd(gd::LayoutHasName(), name));
find_if(scenes.begin(), scenes.end(), [&name](const std::unique_ptr<gd::Layout>& layout) {
return layout->GetName() == name;
});
if (scene == scenes.end()) return;
scenes.erase(scene);
@@ -326,19 +334,24 @@ void Project::RemoveLayout(const gd::String& name) {
bool Project::HasExternalEventsNamed(const gd::String& name) const {
return (find_if(externalEvents.begin(),
externalEvents.end(),
bind2nd(gd::ExternalEventsHasName(), name)) !=
externalEvents.end());
[&name](const std::unique_ptr<gd::ExternalEvents>& externalEvents) {
return externalEvents->GetName() == name;
}) != externalEvents.end());
}
gd::ExternalEvents& Project::GetExternalEvents(const gd::String& name) {
return *(*find_if(externalEvents.begin(),
externalEvents.end(),
bind2nd(gd::ExternalEventsHasName(), name)));
[&name](const std::unique_ptr<gd::ExternalEvents>& externalEvents) {
return externalEvents->GetName() == name;
}));
}
const gd::ExternalEvents& Project::GetExternalEvents(
const gd::String& name) const {
return *(*find_if(externalEvents.begin(),
externalEvents.end(),
bind2nd(gd::ExternalEventsHasName(), name)));
[&name](const std::unique_ptr<gd::ExternalEvents>& externalEvents) {
return externalEvents->GetName() == name;
}));
}
gd::ExternalEvents& Project::GetExternalEvents(std::size_t index) {
return *externalEvents[index];
@@ -382,7 +395,9 @@ void Project::RemoveExternalEvents(const gd::String& name) {
std::vector<std::unique_ptr<gd::ExternalEvents> >::iterator events =
find_if(externalEvents.begin(),
externalEvents.end(),
bind2nd(gd::ExternalEventsHasName(), name));
[&name](const std::unique_ptr<gd::ExternalEvents>& externalEvents) {
return externalEvents->GetName() == name;
});
if (events == externalEvents.end()) return;
externalEvents.erase(events);
@@ -448,19 +463,24 @@ void Project::SwapExternalLayouts(std::size_t first, std::size_t second) {
bool Project::HasExternalLayoutNamed(const gd::String& name) const {
return (find_if(externalLayouts.begin(),
externalLayouts.end(),
bind2nd(gd::ExternalLayoutHasName(), name)) !=
externalLayouts.end());
[&name](const std::unique_ptr<gd::ExternalLayout>& externalLayout) {
return externalLayout->GetName() == name;
}) != externalLayouts.end());
}
gd::ExternalLayout& Project::GetExternalLayout(const gd::String& name) {
return *(*find_if(externalLayouts.begin(),
externalLayouts.end(),
bind2nd(gd::ExternalLayoutHasName(), name)));
[&name](const std::unique_ptr<gd::ExternalLayout>& externalLayout) {
return externalLayout->GetName() == name;
}));
}
const gd::ExternalLayout& Project::GetExternalLayout(
const gd::String& name) const {
return *(*find_if(externalLayouts.begin(),
externalLayouts.end(),
bind2nd(gd::ExternalLayoutHasName(), name)));
[&name](const std::unique_ptr<gd::ExternalLayout>& externalLayout) {
return externalLayout->GetName() == name;
}));
}
gd::ExternalLayout& Project::GetExternalLayout(std::size_t index) {
return *externalLayouts[index];
@@ -504,7 +524,9 @@ void Project::RemoveExternalLayout(const gd::String& name) {
std::vector<std::unique_ptr<gd::ExternalLayout> >::iterator externalLayout =
find_if(externalLayouts.begin(),
externalLayouts.end(),
bind2nd(gd::ExternalLayoutHasName(), name));
[&name](const std::unique_ptr<gd::ExternalLayout>& externalLayout) {
return externalLayout->GetName() == name;
});
if (externalLayout == externalLayouts.end()) return;
externalLayouts.erase(externalLayout);
@@ -1076,7 +1098,9 @@ bool Project::HasSourceFile(gd::String name, gd::String language) const {
vector<std::unique_ptr<SourceFile> >::const_iterator sourceFile =
find_if(externalSourceFiles.begin(),
externalSourceFiles.end(),
bind2nd(gd::ExternalSourceFileHasName(), name));
[&name](const std::unique_ptr<SourceFile>& sourceFile) {
return sourceFile->GetFileName() == name;
});
if (sourceFile == externalSourceFiles.end()) return false;
@@ -1086,20 +1110,26 @@ bool Project::HasSourceFile(gd::String name, gd::String language) const {
gd::SourceFile& Project::GetSourceFile(const gd::String& name) {
return *(*find_if(externalSourceFiles.begin(),
externalSourceFiles.end(),
bind2nd(gd::ExternalSourceFileHasName(), name)));
[&name](const std::unique_ptr<SourceFile>& sourceFile) {
return sourceFile->GetFileName() == name;
}));
}
const gd::SourceFile& Project::GetSourceFile(const gd::String& name) const {
return *(*find_if(externalSourceFiles.begin(),
externalSourceFiles.end(),
bind2nd(gd::ExternalSourceFileHasName(), name)));
[&name](const std::unique_ptr<SourceFile>& sourceFile) {
return sourceFile->GetFileName() == name;
}));
}
void Project::RemoveSourceFile(const gd::String& name) {
std::vector<std::unique_ptr<gd::SourceFile> >::iterator sourceFile =
find_if(externalSourceFiles.begin(),
externalSourceFiles.end(),
bind2nd(gd::ExternalSourceFileHasName(), name));
[&name](const std::unique_ptr<SourceFile>& sourceFile) {
return sourceFile->GetFileName() == name;
});
if (sourceFile == externalSourceFiles.end()) return;
externalSourceFiles.erase(sourceFile);

View File

@@ -13,6 +13,7 @@ class ObjectsContainersList;
class VariablesContainersList;
class PropertiesContainersList;
class NamedPropertyDescriptor;
class ParameterMetadataContainer;
class BaseEvent;
class EventsFunctionsExtension;
class EventsFunction;
@@ -131,7 +132,7 @@ class ProjectScopedContainers {
}
ProjectScopedContainers &AddParameters(
const std::vector<gd::ParameterMetadata> &parameters) {
const ParameterMetadataContainer &parameters) {
parametersVectorsList.push_back(&parameters);
return *this;
@@ -224,7 +225,7 @@ class ProjectScopedContainers {
return propertiesContainersList;
};
const std::vector<const std::vector<gd::ParameterMetadata> *> &GetParametersVectorsList() const {
const std::vector<const ParameterMetadataContainer *> &GetParametersVectorsList() const {
return parametersVectorsList;
};
@@ -236,7 +237,7 @@ class ProjectScopedContainers {
gd::ObjectsContainersList objectsContainersList;
gd::VariablesContainersList variablesContainersList;
gd::PropertiesContainersList propertiesContainersList;
std::vector<const std::vector<gd::ParameterMetadata> *> parametersVectorsList;
std::vector<const ParameterMetadataContainer *> parametersVectorsList;
};
} // namespace gd

View File

@@ -38,6 +38,13 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
if (advanced) {
element.AddChild("advanced").SetBoolValue(advanced);
}
if (quickCustomizationVisibility != QuickCustomization::Visibility::Default) {
element.AddChild("quickCustomizationVisibility")
.SetStringValue(quickCustomizationVisibility ==
QuickCustomization::Visibility::Visible
? "visible"
: "hidden");
}
}
void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
@@ -67,11 +74,21 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
? element.GetChild("hidden").GetBoolValue()
: false;
deprecated = element.HasChild("deprecated")
? element.GetChild("deprecated").GetBoolValue()
: false;
? element.GetChild("deprecated").GetBoolValue()
: false;
advanced = element.HasChild("advanced")
? element.GetChild("advanced").GetBoolValue()
: false;
? element.GetChild("advanced").GetBoolValue()
: false;
if (element.HasChild("quickCustomizationVisibility")) {
quickCustomizationVisibility =
element.GetChild("quickCustomizationVisibility").GetStringValue() ==
"visible"
? QuickCustomization::Visibility::Visible
: QuickCustomization::Visibility::Hidden;
} else {
quickCustomizationVisibility = QuickCustomization::Visibility::Default;
}
}
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {

View File

@@ -9,6 +9,7 @@
#include "GDCore/String.h"
#include "GDCore/Project/MeasurementUnit.h"
#include "GDCore/Project/QuickCustomization.h"
namespace gd {
class SerializerElement;
@@ -32,14 +33,18 @@ class GD_CORE_API PropertyDescriptor {
PropertyDescriptor(gd::String propertyValue)
: currentValue(propertyValue), type("string"), label(""), hidden(false),
deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
hasImpactOnOtherProperties(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
/**
* \brief Empty constructor creating an empty property to be displayed.
*/
PropertyDescriptor()
: hidden(false), deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()){};
hasImpactOnOtherProperties(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
/**
* \brief Destructor
@@ -109,7 +114,7 @@ class GD_CORE_API PropertyDescriptor {
extraInformation.push_back(info);
return *this;
}
/**
* \brief Change the unit of measurement of the property value.
*/
@@ -128,7 +133,7 @@ class GD_CORE_API PropertyDescriptor {
const std::vector<gd::String>& GetExtraInfo() const {
return extraInformation;
}
std::vector<gd::String>& GetExtraInfo() {
return extraInformation;
}
@@ -172,6 +177,28 @@ class GD_CORE_API PropertyDescriptor {
*/
bool IsAdvanced() const { return advanced; }
/**
* \brief Check if the property has impact on other properties - which means a change
* must re-render other properties.
*/
bool HasImpactOnOtherProperties() const { return hasImpactOnOtherProperties; }
/**
* \brief Set if the property has impact on other properties - which means a change
* must re-render other properties.
*/
PropertyDescriptor& SetHasImpactOnOtherProperties(bool enable) {
hasImpactOnOtherProperties = enable;
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const { return quickCustomizationVisibility; }
PropertyDescriptor& SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
return *this;
}
/** \name Serialization
*/
///@{
@@ -211,7 +238,9 @@ class GD_CORE_API PropertyDescriptor {
bool hidden;
bool deprecated;
bool advanced;
bool hasImpactOnOtherProperties;
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
QuickCustomization::Visibility quickCustomizationVisibility;
};
} // namespace gd

View File

@@ -0,0 +1,16 @@
#pragma once
namespace gd {
class QuickCustomization {
public:
enum Visibility {
/** Visibility based on the parent or editor heuristics (probably visible). */
Default,
/** Visible in the quick customization editor. */
Visible,
/** Not visible in the quick customization editor. */
Hidden
};
};
} // namespace gd

View File

@@ -87,20 +87,6 @@ class GD_CORE_API SourceFile {
///< SetAssociatedEvent.
};
//"Tool" Functions
/**
* Functor testing Source Files name
*/
struct ExternalSourceFileHasName
: public std::
binary_function<std::unique_ptr<SourceFile>, gd::String, bool> {
bool operator()(const std::unique_ptr<SourceFile>& externalEvents,
gd::String name) const {
return externalEvents->GetFileName() == name;
}
};
} // namespace gd
#endif // SOURCEFILE_H

View File

@@ -323,6 +323,7 @@ class GD_CORE_API SerializerElement {
gd::String deprecatedName = "") const;
/**
* \deprecated Use HasChild instead. This should be removed from the codebase.
* \brief Return true if the specified attribute exists.
* \param name The name of the attribute to find.
*/

1
Core/GDCore/Tools/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
VersionPriv.h

View File

@@ -1,8 +0,0 @@
// Deprecated version number that was used for GDevelop 4, but still
// used to version the project files.
// Might be a good idea to refactor this at some point to make it
// clearer this is used for the versioning of the project files.
#define GD_VERSION_STRING "4.0.99-0-release"
#define GD_VERSION_RC 4,0,99,0-0-release
#define GD_VERSION_RC_STRING "4, 0, 99, 0-0-release\0"
#define GD_DATE_STRING __DATE__

View File

@@ -24,15 +24,6 @@ int VersionWrapper::Revision() {
: 0;
}
gd::String VersionWrapper::FullString() { return GD_VERSION_STRING; }
gd::String VersionWrapper::Date() {
return gd::String(GD_DATE_STRING).substr(4, 2);
}
gd::String VersionWrapper::Month() {
return gd::String(GD_DATE_STRING).substr(0, 3);
}
gd::String VersionWrapper::Year() {
return gd::String(GD_DATE_STRING).substr(7, 4);
}
gd::String VersionWrapper::Status() {
return Revision() == 0 ? "Release" : "Dev";
}

View File

@@ -46,21 +46,6 @@ class GD_CORE_API VersionWrapper {
*/
static gd::String Status();
/**
* \brief Get Year of the release
*/
static gd::String Year();
/**
* \brief Get Month of the release
*/
static gd::String Month();
/**
* \brief Get Day of the release
*/
static gd::String Date();
/**
* \brief Return true if the first version is older
* than the second version.

View File

@@ -527,15 +527,9 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
}
SECTION("Parameters (1 level)") {
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
parameters.push_back(param1);
parameters.push_back(param2);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("string");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -568,19 +562,10 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
}
SECTION("Parameters (1 level, number|string)") {
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyNumberParameter");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyStringParameter");
param2.SetType("string");
gd::ParameterMetadata param3;
param3.SetName("MyBooleanParameter");
param3.SetType("yesorno");
parameters.push_back(param1);
parameters.push_back(param2);
parameters.push_back(param3);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyNumberParameter", 0).SetType("number");
parameters.InsertNewParameter("MyStringParameter", 1).SetType("string");
parameters.InsertNewParameter("MyBooleanParameter", 2).SetType("yesorno");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);

View File

@@ -1031,25 +1031,10 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Numbers and texts mismatches ('number|string' type, with a parameter first)") {
std::vector<gd::ParameterMetadata> parameters;
{
gd::ParameterMetadata param;
param.SetName("MyNumberParameter");
param.SetType("number");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyStringParameter");
param.SetType("string");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyBooleanParameter");
param.SetType("yesorno");
parameters.push_back(param);
}
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyNumberParameter", 0).SetType("number");
parameters.InsertNewParameter("MyStringParameter", 1).SetType("string");
parameters.InsertNewParameter("MyBooleanParameter", 2).SetType("yesorno");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -2017,19 +2002,10 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
SECTION("Valid parameter") {
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
gd::ParameterMetadata param3;
param3.SetName("MyParameter3");
param3.SetType("yesorno");
parameters.push_back(param1);
parameters.push_back(param2);
parameters.push_back(param3);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("string");
parameters.InsertNewParameter("MyParameter3", 2).SetType("yesorno");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -2098,15 +2074,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
SECTION("Invalid parameter (wrong type)") {
{
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("audioResource");
parameters.push_back(param1);
parameters.push_back(param2);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("audioResource");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -2123,15 +2093,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
SECTION("Invalid parameter (non existing name)") {
{
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
parameters.push_back(param1);
parameters.push_back(param2);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("string");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -2148,15 +2112,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
SECTION("Invalid parameter (unsupported child syntax, 1 level)") {
{
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
parameters.push_back(param1);
parameters.push_back(param2);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("string");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -2172,15 +2130,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
SECTION("Invalid parameter (unsupported child syntax, 2 levels)") {
{
std::vector<gd::ParameterMetadata> parameters;
gd::ParameterMetadata param1;
param1.SetName("MyParameter1");
param1.SetType("number");
gd::ParameterMetadata param2;
param2.SetName("MyParameter2");
param2.SetType("string");
parameters.push_back(param1);
parameters.push_back(param2);
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("string");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -3104,25 +3056,10 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Valid type inferred from expressions with type 'number|string', with a parameter first") {
std::vector<gd::ParameterMetadata> parameters;
{
gd::ParameterMetadata param;
param.SetName("MyNumberParameter");
param.SetType("number");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyStringParameter");
param.SetType("string");
parameters.push_back(param);
}
{
gd::ParameterMetadata param;
param.SetName("MyBooleanParameter");
param.SetType("yesorno");
parameters.push_back(param);
}
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyNumberParameter", 0).SetType("number");
parameters.InsertNewParameter("MyStringParameter", 1).SetType("string");
parameters.InsertNewParameter("MyBooleanParameter", 2).SetType("yesorno");
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);

View File

@@ -79,7 +79,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
"angle of the trajectory direction.");
REQUIRE(getter.GetSentence() == "the movement angle");
// Object and behavior parameters are added automatically.
REQUIRE(getter.GetParameters().size() == 0);
REQUIRE(getter.GetParameters().GetParametersCount() == 0);
REQUIRE(getter.GetEvents().GetEventsCount() == 1);
REQUIRE(getter.GetEvents().GetEvent(0).GetType() ==
@@ -106,7 +106,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
REQUIRE(setter.GetDescription() == "");
REQUIRE(setter.GetSentence() == "");
// Object and behavior parameters are added automatically.
REQUIRE(setter.GetParameters().size() == 0);
REQUIRE(setter.GetParameters().GetParametersCount() == 0);
REQUIRE(setter.GetEvents().GetEventsCount() == 1);
REQUIRE(setter.GetEvents().GetEvent(0).GetType() ==
@@ -195,7 +195,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
"this behavior only.");
REQUIRE(getter.GetSentence() == "_PARAM0_ rotate object");
// Object and behavior parameters are added automatically.
REQUIRE(getter.GetParameters().size() == 0);
REQUIRE(getter.GetParameters().GetParametersCount() == 0);
REQUIRE(getter.GetEvents().GetEventsCount() == 1);
REQUIRE(getter.GetEvents().GetEvent(0).GetType() ==
@@ -232,16 +232,16 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
REQUIRE(setter.GetSentence() == "_PARAM0_ rotate object: _PARAM2_");
// To generate the value parameter, object and behavior parameters has to
// be declared too.
REQUIRE(setter.GetParameters().size() == 3);
auto &objectParameter = setter.GetParameters().at(0);
REQUIRE(setter.GetParameters().GetParametersCount() == 3);
auto &objectParameter = setter.GetParameters().GetParameter(0);
REQUIRE(objectParameter.GetName() == "Object");
REQUIRE(objectParameter.GetType() == "object");
auto &behaviorParameter = setter.GetParameters().at(1);
auto &behaviorParameter = setter.GetParameters().GetParameter(1);
REQUIRE(behaviorParameter.GetName() == "Behavior");
REQUIRE(behaviorParameter.GetType() == "behavior");
REQUIRE(behaviorParameter.GetExtraInfo() ==
"MyEventsExtension::MyEventsBasedBehavior");
auto &valueParameter = setter.GetParameters().at(2);
auto &valueParameter = setter.GetParameters().GetParameter(2);
REQUIRE(valueParameter.GetName() == "Value");
REQUIRE(valueParameter.GetType() == "yesorno");
@@ -329,7 +329,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
"angle of the trajectory direction.");
REQUIRE(getter.GetSentence() == "the movement angle");
// Object parameter is added automatically.
REQUIRE(getter.GetParameters().size() == 0);
REQUIRE(getter.GetParameters().GetParametersCount() == 0);
REQUIRE(getter.GetEvents().GetEventsCount() == 1);
REQUIRE(getter.GetEvents().GetEvent(0).GetType() ==
@@ -356,7 +356,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
REQUIRE(setter.GetDescription() == "");
REQUIRE(setter.GetSentence() == "");
// Object parameter is added automatically.
REQUIRE(setter.GetParameters().size() == 0);
REQUIRE(setter.GetParameters().GetParametersCount() == 0);
REQUIRE(setter.GetEvents().GetEventsCount() == 1);
REQUIRE(setter.GetEvents().GetEvent(0).GetType() ==
@@ -443,7 +443,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
"this object.");
REQUIRE(getter.GetSentence() == "_PARAM0_ rotate object");
// The Object parameter is added automatically.
REQUIRE(getter.GetParameters().size() == 0);
REQUIRE(getter.GetParameters().GetParametersCount() == 0);
REQUIRE(getter.GetEvents().GetEventsCount() == 1);
REQUIRE(getter.GetEvents().GetEvent(0).GetType() ==
@@ -478,13 +478,13 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
REQUIRE(setter.GetSentence() == "_PARAM0_ rotate object: _PARAM1_");
// To generate the value parameter, the object parameter has to
// be declared too.
REQUIRE(setter.GetParameters().size() == 2);
auto &objectParameter = setter.GetParameters().at(0);
REQUIRE(setter.GetParameters().GetParametersCount() == 2);
auto &objectParameter = setter.GetParameters().GetParameter(0);
REQUIRE(objectParameter.GetName() == "Object");
REQUIRE(objectParameter.GetType() == "object");
REQUIRE(objectParameter.GetExtraInfo() ==
"MyEventsExtension::MyEventsBasedObject");
auto &valueParameter = setter.GetParameters().at(1);
auto &valueParameter = setter.GetParameters().GetParameter(1);
REQUIRE(valueParameter.GetName() == "Value");
REQUIRE(valueParameter.GetType() == "yesorno");

View File

@@ -867,26 +867,22 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions();
auto &behaviorAction = behaviorEventsFunctions.InsertNewEventsFunction(
"MyBehaviorEventsFunction", 0);
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("ObjectWithMyBehavior")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("OtherBehavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
behaviorAction.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
behaviorAction.GetParameters()
.InsertNewParameter("Behavior", 1)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
behaviorAction.GetParameters()
.InsertNewParameter("ObjectWithMyBehavior", 2)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
behaviorAction.GetParameters()
.InsertNewParameter("OtherBehavior", 3)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
auto &group = behaviorAction.GetObjectGroups().InsertNew("GroupWithMyBehavior");
group.AddObject("ObjectWithMyBehavior");
@@ -894,36 +890,32 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
behaviorEventsFunctions
.InsertNewEventsFunction("MyBehaviorEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
behaviorExpression.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
behaviorExpression.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
behaviorExpression.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
behaviorExpression.GetParameters()
.InsertNewParameter("Behavior", 1)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
auto &behaviorExpressionAndCondition =
behaviorEventsFunctions
.InsertNewEventsFunction("MyBehaviorEventsFunctionExpressionAndCondition", 2)
.SetFunctionType(gd::EventsFunction::ExpressionAndCondition);
behaviorExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata().SetName("Object").SetType("object"));
behaviorExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyExtension::MyEventsBasedBehavior"));
behaviorExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Value1")
.SetType("expression"));
behaviorExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Value2")
.SetType("expression"));
behaviorExpressionAndCondition.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object");
behaviorExpressionAndCondition.GetParameters()
.InsertNewParameter("Behavior", 1)
.SetType("behavior")
.SetExtraInfo("MyExtension::MyEventsBasedBehavior");
behaviorExpressionAndCondition.GetParameters()
.InsertNewParameter("Value1", 2)
.SetType("expression");
behaviorExpressionAndCondition.GetParameters()
.InsertNewParameter("Value2", 3)
.SetType("expression");
behaviorEventsFunctions
.InsertNewEventsFunction("MyBehaviorEventsFunctionActionWithOperator", 2)
@@ -956,46 +948,41 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &objectEventsFunctions = eventsBasedObject.GetEventsFunctions();
auto &objectAction = objectEventsFunctions.InsertNewEventsFunction(
"MyObjectEventsFunction", 0);
objectAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
objectAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("OtherObject")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
objectAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("OtherBehavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
objectAction.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
objectAction.GetParameters()
.InsertNewParameter("OtherObject", 1)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
objectAction.GetParameters()
.InsertNewParameter("OtherBehavior", 2)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
auto &objectExpression =
objectEventsFunctions
.InsertNewEventsFunction("MyObjectEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
objectExpression.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
objectExpression.GetParameters().InsertNewParameter("Object", 0)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
auto &objectExpressionAndCondition =
objectEventsFunctions
.InsertNewEventsFunction("MyObjectEventsFunctionExpressionAndCondition", 2)
.InsertNewEventsFunction(
"MyObjectEventsFunctionExpressionAndCondition", 2)
.SetFunctionType(gd::EventsFunction::ExpressionAndCondition);
objectExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata().SetName("Object").SetType("object"));
objectExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Value1")
.SetType("expression"));
objectExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Value2")
.SetType("expression"));
objectExpressionAndCondition.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object");
objectExpressionAndCondition.GetParameters()
.InsertNewParameter("Value1", 1)
.SetType("expression");
objectExpressionAndCondition.GetParameters()
.InsertNewParameter("Value2", 2)
.SetType("expression");
objectEventsFunctions
.InsertNewEventsFunction("MyObjectEventsFunctionActionWithOperator", 2)
@@ -1022,32 +1009,27 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions();
auto &behaviorAction = behaviorEventsFunctions.InsertNewEventsFunction(
"MyBehaviorEventsFunction", 0);
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
behaviorAction.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
behaviorAction.GetParameters()
.InsertNewParameter("Behavior", 1)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
// Define the same objects as in the layout to be consistent with events.
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("ObjectWithMyBehavior")
.SetType("object")
.SetExtraInfo("MyExtension::Sprite"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("MyBehavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("MyCustomObject")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
behaviorAction.GetParameters()
.InsertNewParameter("ObjectWithMyBehavior", 2)
.SetType("object")
.SetExtraInfo("MyExtension::Sprite");
behaviorAction.GetParameters()
.InsertNewParameter("MyBehavior", 3)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
behaviorAction.GetParameters()
.InsertNewParameter("MyCustomObject", 4)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
}
// Add an other events based object that uses previously defined events based
@@ -1062,11 +1044,10 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &objectEventsFunctions = eventsBasedObject.GetEventsFunctions();
auto &objectAction = objectEventsFunctions.InsertNewEventsFunction(
"MyObjectEventsFunction", 0);
objectAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyOtherEventsBasedObject"));
objectAction.GetParameters()
.InsertNewParameter("Object", 0)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyOtherEventsBasedObject");
// Add a child-object with the same names the one from the scene
// to be able to use the same events list.
@@ -1091,39 +1072,33 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
{
auto &action =
eventsExtension.InsertNewEventsFunction("MyEventsFunction", 0);
action.GetParameters().push_back(gd::ParameterMetadata()
.SetName("currentScene")
.SetType("")
.SetCodeOnly(true));
action.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Object")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
action.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
action.GetParameters()
.InsertNewParameter("currentScene", 0)
.SetType("")
.SetCodeOnly(true);
action.GetParameters()
.InsertNewParameter("Object", 1)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
action.GetParameters()
.InsertNewParameter("Behavior", 2)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
auto &expression =
eventsExtension.InsertNewEventsFunction("MyEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
expression.GetParameters().push_back(gd::ParameterMetadata()
.SetName("currentScene")
.SetType("")
.SetCodeOnly(true));
expression.GetParameters()
.InsertNewParameter("currentScene", 0)
.SetType("")
.SetCodeOnly(true);
auto &freeExpressionAndCondition = eventsExtension.InsertNewEventsFunction("MyEventsFunctionExpressionAndCondition", 2)
.SetFunctionType(gd::EventsFunction::ExpressionAndCondition);
freeExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Value1")
.SetType("expression"));
freeExpressionAndCondition.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Value2")
.SetType("expression"));
freeExpressionAndCondition.GetParameters().InsertNewParameter("Value1", 0)
.SetType("expression");
freeExpressionAndCondition.GetParameters().InsertNewParameter("Value2", 1)
.SetType("expression");
eventsExtension.InsertNewEventsFunction("MyEventsFunctionActionWithOperator", 2)
.SetFunctionType(gd::EventsFunction::ActionWithOperator)
@@ -1137,21 +1112,18 @@ SetupProjectWithEventsFunctionExtension(gd::Project &project) {
auto &action =
eventsExtension.InsertNewEventsFunction("MyOtherEventsFunction", 0);
// Define the same objects as in the layout to be consistent with events.
action.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("ObjectWithMyBehavior")
.SetType("object")
.SetExtraInfo("MyExtension::Sprite"));
action.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("MyBehavior")
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior"));
action.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("MyCustomObject")
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject"));
action.GetParameters()
.InsertNewParameter("ObjectWithMyBehavior", 0)
.SetType("object")
.SetExtraInfo("MyExtension::Sprite");
action.GetParameters()
.InsertNewParameter("MyBehavior", 1)
.SetType("behavior")
.SetExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
action.GetParameters()
.InsertNewParameter("MyCustomObject", 2)
.SetType("object")
.SetExtraInfo("MyEventsExtension::MyEventsBasedObject");
auto &group = action.GetObjectGroups().InsertNew("GroupWithMyBehavior");
group.AddObject("ObjectWithMyBehavior");
}
@@ -2071,9 +2043,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
auto &myEventsFunction =
project.GetEventsFunctionsExtension("MyEventsExtension")
.GetEventsFunction("MyEventsFunction");
REQUIRE(myEventsFunction.GetParameters().at(1).GetExtraInfo() ==
REQUIRE(myEventsFunction.GetParameters().GetParameter(1).GetExtraInfo() ==
"MyRenamedExtension::MyEventsBasedObject");
REQUIRE(myEventsFunction.GetParameters().at(2).GetExtraInfo() ==
REQUIRE(myEventsFunction.GetParameters().GetParameter(2).GetExtraInfo() ==
"MyRenamedExtension::MyEventsBasedBehavior");
// Behavior function
@@ -2084,9 +2056,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.Get("MyEventsBasedBehavior")
.GetEventsFunctions()
.GetEventsFunction("MyBehaviorEventsFunction");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(2).GetExtraInfo() ==
"MyRenamedExtension::MyEventsBasedObject");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(3).GetExtraInfo() ==
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(2)
.GetExtraInfo() == "MyRenamedExtension::MyEventsBasedObject");
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(3)
.GetExtraInfo() ==
"MyRenamedExtension::MyEventsBasedBehavior");
}
@@ -2098,9 +2073,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.Get("MyEventsBasedObject")
.GetEventsFunctions()
.GetEventsFunction("MyObjectEventsFunction");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(1).GetExtraInfo() ==
"MyRenamedExtension::MyEventsBasedObject");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(2).GetExtraInfo() ==
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(1)
.GetExtraInfo() == "MyRenamedExtension::MyEventsBasedObject");
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(2)
.GetExtraInfo() ==
"MyRenamedExtension::MyEventsBasedBehavior");
}
}
@@ -2343,7 +2321,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
auto &myEventsFunction =
project.GetEventsFunctionsExtension("MyEventsExtension")
.GetEventsFunction("MyEventsFunction");
REQUIRE(myEventsFunction.GetParameters().at(2).GetExtraInfo() ==
REQUIRE(myEventsFunction.GetParameters().GetParameter(2).GetExtraInfo() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
// Behavior function
@@ -2354,7 +2332,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.Get("MyEventsBasedBehavior")
.GetEventsFunctions()
.GetEventsFunction("MyBehaviorEventsFunction");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(3).GetExtraInfo() ==
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(3)
.GetExtraInfo() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
}
@@ -2366,7 +2346,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.Get("MyEventsBasedObject")
.GetEventsFunctions()
.GetEventsFunction("MyObjectEventsFunction");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(2).GetExtraInfo() ==
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(2)
.GetExtraInfo() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
}
}
@@ -2449,7 +2431,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
auto &myEventsFunction =
project.GetEventsFunctionsExtension("MyEventsExtension")
.GetEventsFunction("MyEventsFunction");
REQUIRE(myEventsFunction.GetParameters().at(1).GetExtraInfo() ==
REQUIRE(myEventsFunction.GetParameters().GetParameter(1).GetExtraInfo() ==
"MyEventsExtension::MyRenamedEventsBasedObject");
// Behavior function
@@ -2460,7 +2442,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.Get("MyEventsBasedBehavior")
.GetEventsFunctions()
.GetEventsFunction("MyBehaviorEventsFunction");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(2).GetExtraInfo() ==
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(2)
.GetExtraInfo() ==
"MyEventsExtension::MyRenamedEventsBasedObject");
}
@@ -2472,7 +2456,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.Get("MyEventsBasedObject")
.GetEventsFunctions()
.GetEventsFunction("MyObjectEventsFunction");
REQUIRE(myBehaviorEventsFunction.GetParameters().at(1).GetExtraInfo() ==
REQUIRE(myBehaviorEventsFunction.GetParameters()
.GetParameter(1)
.GetExtraInfo() ==
"MyEventsExtension::MyRenamedEventsBasedObject");
}
}

View File

@@ -164,8 +164,18 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.depth !== undefined)
if (initialInstanceData.depth !== undefined) {
this.setDepth(initialInstanceData.depth);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
if (initialInstanceData.flippedZ) {
this.flipZ(initialInstanceData.flippedZ);
}
}
setX(x: float): void {

View File

@@ -72,8 +72,18 @@ namespace gdjs {
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
super.extraInitializationFromInitialInstance(initialInstanceData);
if (initialInstanceData.depth !== undefined)
if (initialInstanceData.depth !== undefined) {
this.setDepth(initialInstanceData.depth);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
if (initialInstanceData.flippedZ) {
this.flipZ(initialInstanceData.flippedZ);
}
}
/**
@@ -304,8 +314,15 @@ namespace gdjs {
*/
setDepth(depth: float): void {
const unscaledDepth = this.getUnscaledDepth();
if (unscaledDepth !== 0) {
this.setScaleZ(depth / unscaledDepth);
if (unscaledDepth === 0) {
return;
}
const scaleZ = depth / unscaledDepth;
if (this._innerArea && this._isInnerAreaFollowingParentSize) {
this._innerArea.min[2] *= scaleZ;
this._innerArea.max[2] *= scaleZ;
} else {
this.setScaleZ(scaleZ);
}
}
@@ -325,6 +342,10 @@ namespace gdjs {
* @param newScale The new scale (must be greater than 0).
*/
setScaleZ(newScale: number): void {
if (this._innerArea && this._isInnerAreaFollowingParentSize) {
// The scale is always 1;
return;
}
if (newScale < 0) {
newScale = 0;
}

View File

@@ -2216,8 +2216,14 @@ module.exports = {
this._centerY / objectTextureFrame.height;
this._pixiTexturedObject.angle = this._instance.getAngle();
this._pixiTexturedObject.scale.x = width / objectTextureFrame.width;
this._pixiTexturedObject.scale.y = height / objectTextureFrame.height;
const scaleX =
(width / objectTextureFrame.width) *
(this._instance.isFlippedX() ? -1 : 1);
const scaleY =
(height / objectTextureFrame.height) *
(this._instance.isFlippedY() ? -1 : 1);
this._pixiTexturedObject.scale.x = scaleX;
this._pixiTexturedObject.scale.y = scaleY;
this._pixiTexturedObject.position.x =
this._instance.getX() +
@@ -2244,6 +2250,9 @@ module.exports = {
this._pixiFallbackObject.position.y =
this._instance.getY() + height / 2;
this._pixiFallbackObject.angle = this._instance.getAngle();
if (this._instance.isFlippedX()) this._pixiFallbackObject.scale.x = -1;
if (this._instance.isFlippedY()) this._pixiFallbackObject.scale.y = -1;
}
update() {
@@ -2393,12 +2402,16 @@ module.exports = {
RenderedInstance.toRad(this._instance.getAngle())
);
const scaleX = width * (this._instance.isFlippedX() ? -1 : 1);
const scaleY = height * (this._instance.isFlippedY() ? -1 : 1);
const scaleZ = depth * (this._instance.isFlippedZ() ? -1 : 1);
if (
width !== this._threeObject.scale.width ||
height !== this._threeObject.scale.height ||
depth !== this._threeObject.scale.depth
scaleX !== this._threeObject.scale.width ||
scaleY !== this._threeObject.scale.height ||
scaleZ !== this._threeObject.scale.depth
) {
this._threeObject.scale.set(width, height, depth);
this._threeObject.scale.set(scaleX, scaleY, scaleZ);
this.updateTextureUvMapping();
}
}
@@ -3186,12 +3199,16 @@ module.exports = {
RenderedInstance.toRad(this._instance.getAngle())
);
const scaleX = width * (this._instance.isFlippedX() ? -1 : 1);
const scaleY = height * (this._instance.isFlippedY() ? -1 : 1);
const scaleZ = depth * (this._instance.isFlippedZ() ? -1 : 1);
if (
width !== this._threeObject.scale.width ||
height !== this._threeObject.scale.height ||
depth !== this._threeObject.scale.depth
scaleX !== this._threeObject.scale.width ||
scaleY !== this._threeObject.scale.height ||
scaleZ !== this._threeObject.scale.depth
) {
this._threeObject.scale.set(width, height, depth);
this._threeObject.scale.set(scaleX, scaleY, scaleZ);
}
}

View File

@@ -333,6 +333,7 @@ namespace gdjs {
adUnitId,
position: atTop ? 'top' : 'bottom',
size: bannerRequestedAdSizeType,
offset: 0,
});
banner.on('load', () => {

View File

@@ -25,7 +25,6 @@ void AnchorBehavior::InitializeContent(gd::SerializerElement& content) {
content.SetAttribute("useLegacyBottomAndRightAnchors", false);
}
#if defined(GD_IDE_ONLY)
namespace {
gd::String GetAnchorAsString(AnchorBehavior::HorizontalAnchor anchor) {
if (anchor == AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_LEFT)
@@ -34,6 +33,8 @@ gd::String GetAnchorAsString(AnchorBehavior::HorizontalAnchor anchor) {
return _("Window right");
else if (anchor == AnchorBehavior::ANCHOR_HORIZONTAL_PROPORTIONAL)
return _("Proportional");
else if (anchor == AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_CENTER)
return _("Window center");
else
return _("No anchor");
}
@@ -45,6 +46,8 @@ gd::String GetAnchorAsString(AnchorBehavior::VerticalAnchor anchor) {
return _("Window bottom");
else if (anchor == AnchorBehavior::ANCHOR_VERTICAL_PROPORTIONAL)
return _("Proportional");
else if (anchor == AnchorBehavior::ANCHOR_VERTICAL_WINDOW_CENTER)
return _("Window center");
else
return _("No anchor");
}
@@ -63,47 +66,55 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.SetDescription(_("otherwise, objects are anchored according to the "
"window size when the object is created."));
properties[_("Left edge anchor")]
properties["leftEdgeAnchor"]
.SetValue(GetAnchorAsString(static_cast<HorizontalAnchor>(
behaviorContent.GetIntAttribute("leftEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window right"))
.AddExtraInfo(_("Proportional"))
.SetLabel(_("Left edge anchor"))
.SetDescription(_("Anchor the left edge of the object on X axis."));
properties[_("Right edge anchor")]
properties["rightEdgeAnchor"]
.SetValue(GetAnchorAsString(static_cast<HorizontalAnchor>(
behaviorContent.GetIntAttribute("rightEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window right"))
.AddExtraInfo(_("Proportional"))
.SetLabel(_("Right edge anchor"))
.SetDescription(_("Anchor the right edge of the object on X axis."));
properties[_("Top edge anchor")]
properties["topEdgeAnchor"]
.SetValue(GetAnchorAsString(static_cast<VerticalAnchor>(
behaviorContent.GetIntAttribute("topEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window bottom"))
.AddExtraInfo(_("Proportional"))
.SetLabel(_("Top edge anchor"))
.SetDescription(_("Anchor the top edge of the object on Y axis."));
properties[_("Bottom edge anchor")]
properties["bottomEdgeAnchor"]
.SetValue(GetAnchorAsString(static_cast<VerticalAnchor>(
behaviorContent.GetIntAttribute("bottomEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window bottom"))
.AddExtraInfo(_("Proportional"))
.SetLabel(_("Bottom edge anchor"))
.SetDescription(_("Anchor the bottom edge of the object on Y axis."));
properties[("useLegacyBottomAndRightAnchors")]
properties["useLegacyBottomAndRightAnchors"]
.SetLabel(_(
"Stretch object when anchoring right or bottom edge (deprecated, "
"it's recommended to leave this unchecked and anchor both sides if "
@@ -127,6 +138,8 @@ AnchorBehavior::HorizontalAnchor GetHorizontalAnchorFromString(
return AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_RIGHT;
else if (value == _("Proportional"))
return AnchorBehavior::ANCHOR_HORIZONTAL_PROPORTIONAL;
else if (value == _("Window center"))
return AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_CENTER;
else
return AnchorBehavior::ANCHOR_HORIZONTAL_NONE;
}
@@ -139,6 +152,8 @@ AnchorBehavior::VerticalAnchor GetVerticalAnchorFromString(
return AnchorBehavior::ANCHOR_VERTICAL_WINDOW_BOTTOM;
else if (value == _("Proportional"))
return AnchorBehavior::ANCHOR_VERTICAL_PROPORTIONAL;
else if (value == _("Window center"))
return AnchorBehavior::ANCHOR_VERTICAL_WINDOW_CENTER;
else
return AnchorBehavior::ANCHOR_VERTICAL_NONE;
}
@@ -147,20 +162,20 @@ AnchorBehavior::VerticalAnchor GetVerticalAnchorFromString(
bool AnchorBehavior::UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value) {
if (name == _("relativeToOriginalWindowSize"))
if (name == "relativeToOriginalWindowSize")
behaviorContent.SetAttribute("relativeToOriginalWindowSize", value == "1");
else if (name == _("Left edge anchor"))
else if (name == "leftEdgeAnchor")
behaviorContent.SetAttribute(
"leftEdgeAnchor",
static_cast<int>(GetHorizontalAnchorFromString(value)));
else if (name == _("Right edge anchor"))
else if (name == "rightEdgeAnchor")
behaviorContent.SetAttribute(
"rightEdgeAnchor",
static_cast<int>(GetHorizontalAnchorFromString(value)));
else if (name == _("Top edge anchor"))
else if (name == "topEdgeAnchor")
behaviorContent.SetAttribute(
"topEdgeAnchor", static_cast<int>(GetVerticalAnchorFromString(value)));
else if (name == _("Bottom edge anchor"))
else if (name == "bottomEdgeAnchor")
behaviorContent.SetAttribute(
"bottomEdgeAnchor",
static_cast<int>(GetVerticalAnchorFromString(value)));
@@ -172,4 +187,3 @@ bool AnchorBehavior::UpdateProperty(gd::SerializerElement& behaviorContent,
return true;
}
#endif

View File

@@ -3,8 +3,8 @@ GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#ifndef ANCHORBEHAVIOR_H
#define ANCHORBEHAVIOR_H
#pragma once
#include <vector>
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Object.h"
@@ -22,14 +22,16 @@ class GD_EXTENSION_API AnchorBehavior : public gd::Behavior {
ANCHOR_HORIZONTAL_NONE = 0,
ANCHOR_HORIZONTAL_WINDOW_LEFT = 1,
ANCHOR_HORIZONTAL_WINDOW_RIGHT = 2,
ANCHOR_HORIZONTAL_PROPORTIONAL = 3
ANCHOR_HORIZONTAL_PROPORTIONAL = 3,
ANCHOR_HORIZONTAL_WINDOW_CENTER = 4
};
enum VerticalAnchor {
ANCHOR_VERTICAL_NONE = 0,
ANCHOR_VERTICAL_WINDOW_TOP = 1,
ANCHOR_VERTICAL_WINDOW_BOTTOM = 2,
ANCHOR_VERTICAL_PROPORTIONAL = 3
ANCHOR_VERTICAL_PROPORTIONAL = 3,
ANCHOR_VERTICAL_WINDOW_CENTER = 4
};
AnchorBehavior() {};
@@ -47,5 +49,3 @@ class GD_EXTENSION_API AnchorBehavior : public gd::Behavior {
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override;
};
#endif // ANCHORBEHAVIOR_H

View File

@@ -30,5 +30,6 @@ void DeclareAnchorBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/AnchorIcon.png",
"AnchorBehavior",
std::make_shared<AnchorBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
std::make_shared<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
}

View File

@@ -4,10 +4,25 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
const enum HorizontalAnchor {
None = 0,
WindowLeft,
WindowRight,
Proportional,
WindowCenter,
}
const enum VerticalAnchor {
None = 0,
WindowTop,
WindowBottom,
Proportional,
WindowCenter,
}
export class AnchorRuntimeBehavior extends gdjs.RuntimeBehavior {
_relativeToOriginalWindowSize: any;
_leftEdgeAnchor: any;
_rightEdgeAnchor: any;
_leftEdgeAnchor: HorizontalAnchor;
_rightEdgeAnchor: HorizontalAnchor;
_topEdgeAnchor: any;
_bottomEdgeAnchor: any;
_invalidDistances: boolean = true;
@@ -74,14 +89,25 @@ namespace gdjs {
gdjs.AnchorRuntimeBehavior.prototype.doStepPreEvents
) as FloatPoint;
// TODO EBO Make it work with event based objects or hide this behavior for them.
const game = instanceContainer.getGame();
let rendererWidth = game.getGameResolutionWidth();
let rendererHeight = game.getGameResolutionHeight();
let parentMinX = instanceContainer.getUnrotatedViewportMinX();
let parentMinY = instanceContainer.getUnrotatedViewportMinY();
let parentMaxX = instanceContainer.getUnrotatedViewportMaxX();
let parentMaxY = instanceContainer.getUnrotatedViewportMaxY();
let parentCenterX = (parentMaxX + parentMinX) / 2;
let parentCenterY = (parentMaxY + parentMinY) / 2;
let parentWidth = parentMaxX - parentMinX;
let parentHeight = parentMaxY - parentMinY;
const layer = instanceContainer.getLayer(this.owner.getLayer());
if (this._invalidDistances) {
if (this._relativeToOriginalWindowSize) {
rendererWidth = game.getOriginalWidth();
rendererHeight = game.getOriginalHeight();
parentMinX = instanceContainer.getInitialUnrotatedViewportMinX();
parentMinY = instanceContainer.getInitialUnrotatedViewportMinY();
parentMaxX = instanceContainer.getInitialUnrotatedViewportMaxX();
parentMaxY = instanceContainer.getInitialUnrotatedViewportMaxY();
parentCenterX = (parentMaxX + parentMinX) / 2;
parentCenterY = (parentMaxY + parentMinY) / 2;
parentWidth = parentMaxX - parentMinX;
parentHeight = parentMaxY - parentMinY;
}
//Calculate the distances from the window's bounds.
@@ -92,49 +118,28 @@ namespace gdjs {
workingPoint
);
//Left edge
if (
this._leftEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_LEFT
) {
this._leftEdgeDistance = topLeftPixel[0];
} else {
if (
this._leftEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_RIGHT
) {
this._leftEdgeDistance = rendererWidth - topLeftPixel[0];
} else {
if (
this._leftEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.PROPORTIONAL
) {
this._leftEdgeDistance = topLeftPixel[0] / rendererWidth;
}
}
// Left edge
if (this._leftEdgeAnchor === HorizontalAnchor.WindowLeft) {
this._leftEdgeDistance = topLeftPixel[0] - parentMinX;
} else if (this._leftEdgeAnchor === HorizontalAnchor.WindowRight) {
this._leftEdgeDistance = topLeftPixel[0] - parentMaxX;
} else if (this._leftEdgeAnchor === HorizontalAnchor.Proportional) {
this._leftEdgeDistance = (topLeftPixel[0] - parentMinX) / parentWidth;
} else if (this._leftEdgeAnchor === HorizontalAnchor.WindowCenter) {
this._leftEdgeDistance = topLeftPixel[0] - parentCenterX;
}
//Top edge
if (
this._topEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_TOP
) {
this._topEdgeDistance = topLeftPixel[1];
} else {
if (
this._topEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_BOTTOM
) {
this._topEdgeDistance = rendererHeight - topLeftPixel[1];
} else {
if (
this._topEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.PROPORTIONAL
) {
this._topEdgeDistance = topLeftPixel[1] / rendererHeight;
}
}
// Top edge
if (this._topEdgeAnchor === VerticalAnchor.WindowTop) {
this._topEdgeDistance = topLeftPixel[1] - parentMinY;
} else if (this._topEdgeAnchor === VerticalAnchor.WindowBottom) {
this._topEdgeDistance = topLeftPixel[1] - parentMaxY;
} else if (this._topEdgeAnchor === VerticalAnchor.Proportional) {
this._topEdgeDistance = (topLeftPixel[1] - parentMinY) / parentHeight;
} else if (this._topEdgeAnchor === VerticalAnchor.WindowCenter) {
this._topEdgeDistance = topLeftPixel[1] - parentCenterY;
}
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
const bottomRightPixel = layer.convertCoords(
this.owner.getDrawableX() + this.owner.getWidth(),
@@ -143,49 +148,30 @@ namespace gdjs {
workingPoint
);
//Right edge
if (
this._rightEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_LEFT
) {
this._rightEdgeDistance = bottomRightPixel[0];
} else {
if (
this._rightEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_RIGHT
) {
this._rightEdgeDistance = rendererWidth - bottomRightPixel[0];
} else {
if (
this._rightEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.PROPORTIONAL
) {
this._rightEdgeDistance = bottomRightPixel[0] / rendererWidth;
}
}
// Right edge
if (this._rightEdgeAnchor === HorizontalAnchor.WindowLeft) {
this._rightEdgeDistance = bottomRightPixel[0] - parentMinX;
} else if (this._rightEdgeAnchor === HorizontalAnchor.WindowRight) {
this._rightEdgeDistance = bottomRightPixel[0] - parentMaxX;
} else if (this._rightEdgeAnchor === HorizontalAnchor.Proportional) {
this._rightEdgeDistance =
(bottomRightPixel[0] - parentMinX) / parentWidth;
} else if (this._rightEdgeAnchor === HorizontalAnchor.WindowCenter) {
this._rightEdgeDistance = bottomRightPixel[0] - parentCenterX;
}
//Bottom edge
if (
this._bottomEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_TOP
) {
this._bottomEdgeDistance = bottomRightPixel[1];
} else {
if (
this._bottomEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_BOTTOM
) {
this._bottomEdgeDistance = rendererHeight - bottomRightPixel[1];
} else {
if (
this._bottomEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.PROPORTIONAL
) {
this._bottomEdgeDistance = bottomRightPixel[1] / rendererHeight;
}
}
// Bottom edge
if (this._bottomEdgeAnchor === VerticalAnchor.WindowTop) {
this._bottomEdgeDistance = bottomRightPixel[1] - parentMinY;
} else if (this._bottomEdgeAnchor === VerticalAnchor.WindowBottom) {
this._bottomEdgeDistance = bottomRightPixel[1] - parentMaxY;
} else if (this._bottomEdgeAnchor === VerticalAnchor.Proportional) {
this._bottomEdgeDistance =
(bottomRightPixel[1] - parentMinY) / parentHeight;
} else if (this._bottomEdgeAnchor === VerticalAnchor.WindowCenter) {
this._bottomEdgeDistance = bottomRightPixel[1] - parentCenterY;
}
this._invalidDistances = false;
} else {
//Move and resize the object if needed
@@ -194,93 +180,50 @@ namespace gdjs {
let rightPixel = 0;
let bottomPixel = 0;
//Left edge
if (
this._leftEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_LEFT
) {
leftPixel = this._leftEdgeDistance;
} else {
if (
this._leftEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_RIGHT
) {
leftPixel = rendererWidth - this._leftEdgeDistance;
} else {
if (
this._leftEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.PROPORTIONAL
) {
leftPixel = this._leftEdgeDistance * rendererWidth;
}
}
// Left edge
if (this._leftEdgeAnchor === HorizontalAnchor.WindowLeft) {
leftPixel = parentMinX + this._leftEdgeDistance;
} else if (this._leftEdgeAnchor === HorizontalAnchor.WindowRight) {
leftPixel = parentMaxX + this._leftEdgeDistance;
} else if (this._leftEdgeAnchor === HorizontalAnchor.Proportional) {
leftPixel = parentMinX + this._leftEdgeDistance * parentWidth;
} else if (this._leftEdgeAnchor === HorizontalAnchor.WindowCenter) {
leftPixel = parentCenterX + this._leftEdgeDistance;
}
//Top edge
if (
this._topEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_TOP
) {
topPixel = this._topEdgeDistance;
} else {
if (
this._topEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_BOTTOM
) {
topPixel = rendererHeight - this._topEdgeDistance;
} else {
if (
this._topEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.PROPORTIONAL
) {
topPixel = this._topEdgeDistance * rendererHeight;
}
}
// Top edge
if (this._topEdgeAnchor === VerticalAnchor.WindowTop) {
topPixel = parentMinY + this._topEdgeDistance;
} else if (this._topEdgeAnchor === VerticalAnchor.WindowBottom) {
topPixel = parentMaxY + this._topEdgeDistance;
} else if (this._topEdgeAnchor === VerticalAnchor.Proportional) {
topPixel = parentMinY + this._topEdgeDistance * parentHeight;
} else if (this._topEdgeAnchor === VerticalAnchor.WindowCenter) {
topPixel = parentCenterY + this._topEdgeDistance;
}
//Right edge
if (
this._rightEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_LEFT
) {
rightPixel = this._rightEdgeDistance;
} else {
if (
this._rightEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.WINDOW_RIGHT
) {
rightPixel = rendererWidth - this._rightEdgeDistance;
} else {
if (
this._rightEdgeAnchor ===
AnchorRuntimeBehavior.HorizontalAnchor.PROPORTIONAL
) {
rightPixel = this._rightEdgeDistance * rendererWidth;
}
}
// Right edge
if (this._rightEdgeAnchor === HorizontalAnchor.WindowLeft) {
rightPixel = parentMinX + this._rightEdgeDistance;
} else if (this._rightEdgeAnchor === HorizontalAnchor.WindowRight) {
rightPixel = parentMaxX + this._rightEdgeDistance;
} else if (this._rightEdgeAnchor === HorizontalAnchor.Proportional) {
rightPixel = parentMinX + this._rightEdgeDistance * parentWidth;
} else if (this._rightEdgeAnchor === HorizontalAnchor.WindowCenter) {
rightPixel = parentCenterX + this._rightEdgeDistance;
}
//Bottom edge
if (
this._bottomEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_TOP
) {
bottomPixel = this._bottomEdgeDistance;
} else {
if (
this._bottomEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.WINDOW_BOTTOM
) {
bottomPixel = rendererHeight - this._bottomEdgeDistance;
} else {
if (
this._bottomEdgeAnchor ===
AnchorRuntimeBehavior.VerticalAnchor.PROPORTIONAL
) {
bottomPixel = this._bottomEdgeDistance * rendererHeight;
}
}
// Bottom edge
if (this._bottomEdgeAnchor === VerticalAnchor.WindowTop) {
bottomPixel = parentMinY + this._bottomEdgeDistance;
} else if (this._bottomEdgeAnchor === VerticalAnchor.WindowBottom) {
bottomPixel = parentMaxY + this._bottomEdgeDistance;
} else if (this._bottomEdgeAnchor === VerticalAnchor.Proportional) {
bottomPixel = parentMinY + this._bottomEdgeDistance * parentHeight;
} else if (this._bottomEdgeAnchor === VerticalAnchor.WindowCenter) {
bottomPixel = parentCenterY + this._bottomEdgeDistance;
}
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
const topLeftCoord = layer.convertInverseCoords(
leftPixel,
@@ -303,27 +246,18 @@ namespace gdjs {
// Compatibility with GD <= 5.0.133
if (this._useLegacyBottomAndRightAnchors) {
//Move and resize the object according to the anchors
if (
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
if (this._rightEdgeAnchor !== HorizontalAnchor.None) {
this.owner.setWidth(right - left);
}
if (
this._bottomEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
if (this._bottomEdgeAnchor !== VerticalAnchor.None) {
this.owner.setHeight(bottom - top);
}
if (
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
if (this._leftEdgeAnchor !== HorizontalAnchor.None) {
this.owner.setX(
left + this.owner.getX() - this.owner.getDrawableX()
);
}
if (
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
if (this._topEdgeAnchor !== VerticalAnchor.None) {
this.owner.setY(
top + this.owner.getY() - this.owner.getDrawableY()
);
@@ -333,25 +267,18 @@ namespace gdjs {
else {
// Resize if right and left anchors are set
if (
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE &&
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
this._rightEdgeAnchor !== HorizontalAnchor.None &&
this._leftEdgeAnchor !== HorizontalAnchor.None
) {
this.owner.setWidth(right - left);
this.owner.setX(left);
} else {
if (
this._leftEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
if (this._leftEdgeAnchor !== HorizontalAnchor.None) {
this.owner.setX(
left + this.owner.getX() - this.owner.getDrawableX()
);
}
if (
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
if (this._rightEdgeAnchor !== HorizontalAnchor.None) {
this.owner.setX(
right +
this.owner.getX() -
@@ -362,24 +289,18 @@ namespace gdjs {
}
// Resize if top and bottom anchors are set
if (
this._bottomEdgeAnchor !==
AnchorRuntimeBehavior.VerticalAnchor.NONE &&
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
this._bottomEdgeAnchor !== VerticalAnchor.None &&
this._topEdgeAnchor !== VerticalAnchor.None
) {
this.owner.setHeight(bottom - top);
this.owner.setY(top);
} else {
if (
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
if (this._topEdgeAnchor !== VerticalAnchor.None) {
this.owner.setY(
top + this.owner.getY() - this.owner.getDrawableY()
);
}
if (
this._bottomEdgeAnchor !==
AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
if (this._bottomEdgeAnchor !== VerticalAnchor.None) {
this.owner.setY(
bottom +
this.owner.getY() -
@@ -393,19 +314,6 @@ namespace gdjs {
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
static HorizontalAnchor = {
NONE: 0,
WINDOW_LEFT: 1,
WINDOW_RIGHT: 2,
PROPORTIONAL: 3,
};
static VerticalAnchor = {
NONE: 0,
WINDOW_TOP: 1,
WINDOW_BOTTOM: 2,
PROPORTIONAL: 3,
};
}
gdjs.registerBehavior(
'AnchorBehavior::AnchorBehavior',

View File

@@ -36,6 +36,13 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
usedExtensionsWithVariablesData: [],
});
const setGameResolutionSizeAndStep = (width, height) => {
runtimeGame.setGameResolutionSize(width, height);
// This method is called by the main loop:
runtimeScene.onGameResolutionResized();
runtimeScene.renderAndStep(1000 / 60);
};
function createObject(behaviorProperties) {
const object = new gdjs.TestRuntimeObject(runtimeScene, {
name: 'obj1',
@@ -67,13 +74,10 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
['rightEdgeAnchor', 'leftEdgeAnchor'].forEach((objectEdge) => {
it(`anchors the ${objectEdge} edge of object to window left (fixed)`, function () {
const object = createObject({ [objectEdge]: 1 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -83,29 +87,36 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
['rightEdgeAnchor', 'leftEdgeAnchor'].forEach((objectEdge) => {
it(`anchors the ${objectEdge} edge of object to window right (fixed)`, function () {
const object = createObject({ [objectEdge]: 2 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
setGameResolutionSizeAndStep(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(1500);
expect(object.getY()).to.equal(500);
expect(object.getWidth()).to.equal(10);
});
});
['rightEdgeAnchor', 'leftEdgeAnchor'].forEach((objectEdge) => {
it(`anchors the ${objectEdge} edge of object to window center (fixed)`, function () {
const object = createObject({ [objectEdge]: 4 });
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(1000);
expect(object.getY()).to.equal(500);
expect(object.getWidth()).to.equal(10);
});
});
it('anchors the right and left edge of object (fixed)', function () {
const object = createObject({ leftEdgeAnchor: 1, rightEdgeAnchor: 2 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -114,13 +125,10 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
it('anchors the left edge of object (proportional)', function () {
const object = createObject({ leftEdgeAnchor: 3 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(1000);
expect(object.getY()).to.equal(500);
@@ -132,13 +140,10 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
['topEdgeAnchor', 'bottomEdgeAnchor'].forEach((objectEdge) => {
it(`anchors the ${objectEdge} edge of object to window top (fixed)`, function () {
const object = createObject({ [objectEdge]: 1 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -148,29 +153,36 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
['topEdgeAnchor', 'bottomEdgeAnchor'].forEach((objectEdge) => {
it(`anchors the ${objectEdge} edge of object to window bottom (fixed)`, function () {
const object = createObject({ [objectEdge]: 2 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(1500);
expect(object.getWidth()).to.equal(10);
});
});
['topEdgeAnchor', 'bottomEdgeAnchor'].forEach((objectEdge) => {
it(`anchors the ${objectEdge} edge of object to window center (fixed)`, function () {
const object = createObject({ [objectEdge]: 4 });
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(1000);
expect(object.getWidth()).to.equal(10);
});
});
it('anchors the top and bottom edge of object (fixed)', function () {
const object = createObject({ topEdgeAnchor: 1, bottomEdgeAnchor: 2 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -179,13 +191,10 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
it('anchors the top edge of object (proportional)', function () {
const object = createObject({ topEdgeAnchor: 3 });
runtimeGame.setGameResolutionSize(1000, 1000);
object.setPosition(500, 500);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
setGameResolutionSizeAndStep(2000, 2000);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(1000);

View File

@@ -66,13 +66,6 @@ module.exports = {
.setLabel(_('Base color'))
.setGroup(_('Appearance'));
objectProperties
.getOrCreate('opacity')
.setValue(objectContent.opacity.toString())
.setType('number')
.setLabel(_('Opacity (0-255)'))
.setGroup(_('Appearance'));
objectProperties
.getOrCreate('fontSize')
.setValue(objectContent.fontSize.toString())
@@ -545,9 +538,6 @@ module.exports = {
this._pixiObject.text = rawText;
}
const opacity = +properties.get('opacity').getValue();
this._pixiObject.alpha = opacity / 255;
const color = properties.get('color').getValue();
this._pixiObject.textStyles.default.fill = objectsRenderingService.rgbOrHexToHexNumber(
color
@@ -607,6 +597,13 @@ module.exports = {
this._pixiObject.dirty = true;
}
}
// Do not hide completely an object so it can still be manipulated
const alphaForDisplay = Math.max(
this._instance.getOpacity() / 255,
0.5
);
this._pixiObject.alpha = alphaForDisplay;
}
/**

View File

@@ -192,6 +192,9 @@ namespace gdjs {
250
);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
onDestroyed(): void {

View File

@@ -59,13 +59,6 @@ module.exports = {
.setType('textarea')
.setLabel(_('Text'));
objectProperties
.getOrCreate('opacity')
.setValue(objectContent.opacity.toString())
.setType('number')
.setLabel(_('Opacity (0-255)'))
.setGroup(_('Appearance'));
objectProperties
.getOrCreate('align')
.setValue(objectContent.align)
@@ -673,9 +666,6 @@ module.exports = {
const rawText = properties.get('text').getValue();
this._pixiObject.text = rawText;
const opacity = +properties.get('opacity').getValue();
this._pixiObject.alpha = opacity / 255;
const align = properties.get('align').getValue();
this._pixiObject.align = align;
@@ -739,6 +729,13 @@ module.exports = {
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
// Do not hide completely an object so it can still be manipulated
const alphaForDisplay = Math.max(
this._instance.getOpacity() / 255,
0.5
);
this._pixiObject.alpha = alphaForDisplay;
}
onRemovedFromScene() {

View File

@@ -203,6 +203,9 @@ namespace gdjs {
if (initialInstanceData.customSize) {
this.setWrappingWidth(initialInstanceData.width);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
onDestroyed(): void {

View File

@@ -34,7 +34,8 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/destroyoutsideicon.png",
"DestroyOutsideBehavior",
std::make_shared<DestroyOutsideBehavior>(),
std::shared_ptr<gd::BehaviorsSharedData>());
std::shared_ptr<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
aut.AddCondition("ExtraBorder",
_("Additional border"),

View File

@@ -52,7 +52,7 @@ module.exports = {
.setType('number');
adjustmentProperties
.getOrCreate('saturation')
.setValue('2')
.setValue('1')
.setLabel(_('Saturation (between 0 and 5)'))
.setType('number');
adjustmentProperties
@@ -77,7 +77,7 @@ module.exports = {
.setType('number');
adjustmentProperties
.getOrCreate('blue')
.setValue('0.6')
.setValue('1')
.setLabel(_('Blue (between 0 and 5)'))
.setType('number');
adjustmentProperties

View File

@@ -30,7 +30,13 @@ namespace gdjs {
if (typeof firebaseConfig !== 'object') return;
if (firebase.apps.length !== 0) await firebase.app().delete();
firebase.initializeApp(firebaseConfig);
for (let func of onAppCreated) func();
for (let func of onAppCreated) {
try {
func();
} catch (e) {
logger.error('An error occurred while running a callback: ' + e);
}
}
};
gdjs.registerFirstRuntimeSceneLoadedCallback(_setupFirebase);

View File

@@ -369,6 +369,36 @@ module.exports = {
'gdjs.multiplayerMessageManager.hasCustomMessageBeenReceived'
);
extension
.addExpressionAndConditionAndAction(
'number',
'ObjectsSynchronizationRate',
_('Objects synchronization rate'),
_(
'objects synchronization rate (between 1 and 60, default is 30 times per second)'
),
_('objects synchronization rate'),
_('Advanced'),
'JsPlatform/Extensions/multiplayer.svg'
)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(_('Sync rate'))
)
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.setObjectsSynchronizationRate')
.setGetter('gdjs.multiplayer.getObjectsSynchronizationRate');
extension
.addCondition(
'IsPlayerHost',
@@ -392,13 +422,13 @@ module.exports = {
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.isPlayerHost');
.setFunctionName('gdjs.multiplayer.isCurrentPlayerHost');
extension
.addCondition(
'HasAnyPlayerLeft',
_('Any player has left'),
_('Check if any player has left the lobby.'),
_('Check if any player has left the lobby game.'),
_('Any player has left'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
@@ -423,7 +453,7 @@ module.exports = {
.addCondition(
'HasPlayerLeft',
_('Player has left'),
_('Check if the player has left the lobby.'),
_('Check if the player has left the lobby game.'),
_('Player _PARAM0_ has left'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
@@ -448,8 +478,10 @@ module.exports = {
extension
.addExpression(
'LastLeftPlayerNumber',
_('Last left player number'),
_('Returns the number of the player that has just left the lobby.'),
_('Player number that just left'),
_(
'Returns the player number of the player that has just left the lobby.'
),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg'
)
@@ -524,8 +556,10 @@ module.exports = {
extension
.addExpression(
'LastJoinedPlayerNumber',
_('Last joined player number'),
_('Returns the number of the player that has just joined the lobby.'),
_('Player number that just joined'),
_(
'Returns the player number of the player that has just joined the lobby.'
),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg'
)
@@ -546,6 +580,61 @@ module.exports = {
'gdjs.multiplayerMessageManager.getLatestPlayerWhoJustJoined'
);
extension
.addCondition(
'IsMigratingHost',
_('Host is migrating'),
_(
'Check if the host is migrating, in order to adapt the game state (like pausing the game).'
),
_('Host is migrating'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
'JsPlatform/Extensions/multiplayer.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.isMigratingHost');
extension
.addAction(
'EndLobbyWhenHostLeaves',
_('Configure lobby game to end when host leaves'),
_(
'Configure the lobby game to end when the host leaves. This will trigger the "Lobby game has just ended" condition. (Default behavior is to migrate the host)'
),
_('Configure lobby game to end when host leaves'),
_('Advanced'),
'JsPlatform/Extensions/multiplayer.svg',
'JsPlatform/Extensions/multiplayer.svg'
)
.addParameter('yesorno', _('End lobby game when host leaves'), '', false)
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.endLobbyWhenHostLeaves');
extension
.addStrExpression(
'MessageData',
@@ -997,6 +1086,7 @@ module.exports = {
multiplayerObjectBehavior,
sharedData
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(

View File

@@ -145,7 +145,7 @@ namespace gdjs {
} = {};
// The number of times per second the scene data should be synchronized.
const sceneSyncDataTickRate = 1;
const sceneSyncDataSyncRate = 1;
let lastSceneSyncTimestamp = 0;
let lastSentSceneSyncData: LayoutNetworkSyncData | null = null;
let numberOfForcedSceneUpdates = 0;
@@ -154,7 +154,7 @@ namespace gdjs {
>();
// The number of times per second the game data should be synchronized.
const gameSyncDataTickRate = 1;
const gameSyncDataSyncRate = 1;
let lastGameSyncTimestamp = 0;
let lastSentGameSyncData: GameNetworkSyncData | null = null;
let numberOfForcedGameUpdates = 0;
@@ -164,8 +164,8 @@ namespace gdjs {
// Send heartbeat messages from host to players, ensuring their connection is still alive,
// measure the ping, and send other useful info.
const heartbeatTickRate = 1;
let lastHeartbeatTimestamp = 0;
const heartbeatSyncRate = 1;
let lastHeartbeatSentTimestamp = 0;
let _playersLastRoundTripTimes: {
[playerNumber: number]: number[];
} = {};
@@ -531,7 +531,10 @@ namespace gdjs {
currentPlayerObjectOwnership === previousOwner ||
// the object is already owned by the new owner. (may have been changed by another player faster)
currentPlayerObjectOwnership === newOwner;
if (gdjs.multiplayer.isPlayerHost() && !ownershipChangeIsCoherent) {
if (
gdjs.multiplayer.isCurrentPlayerHost() &&
!ownershipChangeIsCoherent
) {
// We received an ownership change message for an object which is in an unexpected state.
// There may be some lag, and multiple ownership changes may have been sent by the other players.
// As the host, let's not change the ownership and let the player revert it.
@@ -560,7 +563,7 @@ namespace gdjs {
// If we are the host,
// so we need to relay the ownership change to others,
// and expect an acknowledgment from them.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
// We don't need to send the message to the player who sent the ownership change message.
const otherPeerIds = connectedPeerIds.filter(
@@ -738,7 +741,7 @@ namespace gdjs {
// If we are are the host,
// we need to relay the position to others except the player who sent the update message.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const otherPeerIds = connectedPeerIds.filter(
(peerId) => peerId !== messageSender
@@ -863,7 +866,10 @@ namespace gdjs {
currentPlayerVariableOwnership === previousOwner ||
// the variable is already owned by the new owner. (may have been changed by another player faster)
currentPlayerVariableOwnership === newOwner;
if (gdjs.multiplayer.isPlayerHost() && !ownershipChangeIsCoherent) {
if (
gdjs.multiplayer.isCurrentPlayerHost() &&
!ownershipChangeIsCoherent
) {
// We received an ownership change message for a variable which is in an unexpected state.
// There may be some lag, and multiple ownership changes may have been sent by the other players.
// As the host, let's not change the ownership and let the player revert it.
@@ -892,7 +898,7 @@ namespace gdjs {
// If we are the host,
// we need to relay the ownership change to others,
// and expect an acknowledgment from them.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
// We don't need to send the message to the player who sent the ownership change message.
const otherPeerIds = connectedPeerIds.filter(
@@ -1336,7 +1342,7 @@ namespace gdjs {
// If we are the host, we need to relay the destruction to others.
// And expect an acknowledgment from everyone else as well.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
// We don't need to send the message to the player who sent the destroy message.
const otherPeerIds = connectedPeerIds.filter(
@@ -1429,7 +1435,7 @@ namespace gdjs {
// If we are the host, we can consider this messaged as received
// and add it to the list of custom messages to process on top of the messages received.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const messagesList = gdjs.multiplayerPeerJsHelper.getOrCreateMessagesList(
messageName
);
@@ -1592,7 +1598,7 @@ namespace gdjs {
// If we are the host,
// so we need to relay the message to others.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
// In the case of custom messages, we relay the message to all players, including the sender.
// This allows the sender to process it the same way others would, when they receive the event.
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
@@ -1650,7 +1656,7 @@ namespace gdjs {
const hasSceneBeenSyncedRecently = () => {
return (
getTimeNow() - lastSceneSyncTimestamp < 1000 / sceneSyncDataTickRate
getTimeNow() - lastSceneSyncTimestamp < 1000 / sceneSyncDataSyncRate
);
};
@@ -1664,6 +1670,7 @@ namespace gdjs {
const sceneNetworkSyncData = runtimeScene.getNetworkSyncData({
playerNumber: gdjs.multiplayer.getCurrentPlayerNumber(),
isHost: gdjs.multiplayer.isCurrentPlayerHost(),
});
if (!sceneNetworkSyncData) {
return;
@@ -1737,7 +1744,7 @@ namespace gdjs {
// If we are are the host,
// we need to relay the scene update to others except the player who sent the update message.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
// We don't need to send the message to the player who sent the update message.
const otherPeerIds = connectedPeerIds.filter(
@@ -1814,7 +1821,7 @@ namespace gdjs {
};
const hasGameBeenSyncedRecently = () => {
return getTimeNow() - lastGameSyncTimestamp < 1000 / gameSyncDataTickRate;
return getTimeNow() - lastGameSyncTimestamp < 1000 / gameSyncDataSyncRate;
};
const handleUpdateGameMessagesToSend = (
@@ -1827,6 +1834,7 @@ namespace gdjs {
const gameNetworkSyncData = runtimeScene.getGame().getNetworkSyncData({
playerNumber: gdjs.multiplayer.getCurrentPlayerNumber(),
isHost: gdjs.multiplayer.isCurrentPlayerHost(),
});
if (!gameNetworkSyncData) {
return;
@@ -1888,7 +1896,7 @@ namespace gdjs {
// If we are are the host,
// we need to relay the game update to others except the player who sent the update message.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
// We don't need to send the message to the player who sent the update message.
const otherPeerIds = connectedPeerIds.filter(
@@ -1937,9 +1945,10 @@ namespace gdjs {
messageName: string;
messageData: any;
} => {
// Ensure player 1 is correctly set when the first heartbeat is sent.
_playersInfo[1] = {
ping: 0, // Player 1 is the host, so we don't need to compute the ping.
// If we create the heartbeat meassage, we are the host,
// Ensure our player number is correctly set when the first heartbeat is sent.
_playersInfo[gdjs.multiplayer.getCurrentPlayerNumber()] = {
ping: 0, // we are the host, so we don't need to compute the ping.
playerId: gdjs.playerAuthentication.getUserId(),
username: gdjs.playerAuthentication.getUsername(),
};
@@ -1976,15 +1985,15 @@ namespace gdjs {
};
const hasSentHeartbeatRecently = () => {
return (
!!lastHeartbeatTimestamp &&
getTimeNow() - lastHeartbeatTimestamp < 1000 / heartbeatTickRate
!!lastHeartbeatSentTimestamp &&
getTimeNow() - lastHeartbeatSentTimestamp < 1000 / heartbeatSyncRate
);
};
const handleHeartbeatsToSend = () => {
// Only host sends heartbeats to all players regularly:
// - it allows them to send a heartbeat back immediately so that the host can compute the ping.
// - it allows to pass along the pings of all players to all players.
if (!gdjs.multiplayer.isPlayerHost()) {
if (!gdjs.multiplayer.isCurrentPlayerHost()) {
return;
}
@@ -1997,7 +2006,7 @@ namespace gdjs {
const { messageName, messageData } = createHeartbeatMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
lastHeartbeatTimestamp = getTimeNow();
lastHeartbeatSentTimestamp = getTimeNow();
};
const handleHeartbeatsReceived = () => {
@@ -2024,7 +2033,7 @@ namespace gdjs {
// If we are not the host, save what the host told us about the other players info
// and respond with a heartbeat immediately, informing the host of our playerId and username.
if (!gdjs.multiplayer.isPlayerHost()) {
if (!gdjs.multiplayer.isCurrentPlayerHost()) {
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
const currentlyKnownPlayerNumbers = Object.keys(
_playersInfo
@@ -2134,12 +2143,20 @@ namespace gdjs {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const { messageName, messageData } = createHeartbeatMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
lastHeartbeatTimestamp = getTimeNow();
lastHeartbeatSentTimestamp = getTimeNow();
}
});
});
};
const hasReceivedHeartbeatFromPlayer = (playerNumber: number) => {
// Consider that a player has sent a heartbeat if we have been able to calculate
// at least one round trip time for them.
const playerLastRoundTripTimes =
_playersLastRoundTripTimes[playerNumber] || [];
return playerLastRoundTripTimes.length > 0;
};
const getPlayerPing = (playerNumber: number) => {
const playerInfo = _playersInfo[playerNumber];
if (!playerInfo) {
@@ -2153,7 +2170,15 @@ namespace gdjs {
return getPlayerPing(currentPlayerNumber);
};
const markPlayerAsDisconnected = (playerNumber: number) => {
const markPlayerAsDisconnected = ({
runtimeScene,
playerNumber,
peerId,
}: {
runtimeScene: gdjs.RuntimeScene;
playerNumber: number;
peerId?: string;
}) => {
logger.info(`Marking player ${playerNumber} as disconnected.`);
_playerNumbersWhoJustLeft.push(playerNumber);
// Temporarily save the username in another variable to be used for the notification,
@@ -2161,23 +2186,31 @@ namespace gdjs {
_temporaryPlayerNumberToUsername[playerNumber] = getPlayerUsername(
playerNumber
);
clearPlayerTempData(playerNumber);
// If Player 1 has disconnected, just end the game.
if (playerNumber === 1) {
logger.info('Host has disconnected, ending the game.');
clearAllMessagesTempData();
gdjs.multiplayer.handleLobbyGameEnded();
return;
// If Host has disconnected, either switch host or stop the game.
if (peerId && peerId === gdjs.multiplayer.hostPeerId) {
const shouldEndLobbyGame = gdjs.multiplayer.shouldEndLobbyWhenHostLeaves();
if (shouldEndLobbyGame) {
logger.info('Host has disconnected, ending the game.');
clearAllMessagesTempData();
gdjs.multiplayer.handleLobbyGameEnded();
} else {
logger.info('Host has disconnected, switching host.');
gdjs.multiplayer.handleHostDisconnected({ runtimeScene });
return;
}
}
clearPlayerTempData(playerNumber);
// If we are the host, send a heartbeat right away so that everyone is aware of the disconnection
// on approximately the same frame.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const { messageName, messageData } = createHeartbeatMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
lastHeartbeatTimestamp = getTimeNow();
lastHeartbeatSentTimestamp = getTimeNow();
}
};
@@ -2200,7 +2233,10 @@ namespace gdjs {
}
// We rely on the p2p helper to know who has disconnected.
const justDisconnectedPlayerNumbers: number[] = [];
const justDisconnectedPlayers: {
playerNumber: number;
peerId: string;
}[] = [];
const justDisconnectedPeers = gdjs.multiplayerPeerJsHelper.getJustDisconnectedPeers();
if (justDisconnectedPeers.length) {
@@ -2212,14 +2248,17 @@ namespace gdjs {
return;
}
logger.info(`Player ${disconnectedPlayerNumber} has disconnected.`);
justDisconnectedPlayerNumbers.push(disconnectedPlayerNumber);
justDisconnectedPlayers.push({
playerNumber: disconnectedPlayerNumber,
peerId: disconnectedPeer,
});
}
}
for (const playerNumber of justDisconnectedPlayerNumbers) {
for (const { playerNumber, peerId } of justDisconnectedPlayers) {
// When a player disconnects, as the host, we look at all the instances
// they own and decide what to do with them.
if (gdjs.multiplayer.isPlayerHost()) {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const instances = runtimeScene.getAdhocListOfAllInstances();
for (const instance of instances) {
const behavior = instance.getBehavior(
@@ -2243,7 +2282,7 @@ namespace gdjs {
}
}
markPlayerAsDisconnected(playerNumber);
markPlayerAsDisconnected({ runtimeScene, playerNumber, peerId });
}
};
@@ -2303,6 +2342,10 @@ namespace gdjs {
return _playersInfo[playerNumber] !== undefined;
};
const getPlayersInfo = () => {
return _playersInfo;
};
const endGameMessageName = '#endGame';
const createEndGameMessage = (): {
messageName: string;
@@ -2315,7 +2358,7 @@ namespace gdjs {
};
const sendEndGameMessage = () => {
// Only the host can end the game.
if (!gdjs.multiplayer.isPlayerHost()) {
if (!gdjs.multiplayer.isCurrentPlayerHost()) {
return;
}
@@ -2327,8 +2370,8 @@ namespace gdjs {
sendDataTo(connectedPeerIds, messageName, messageData);
};
const handleEndGameMessages = () => {
if (gdjs.multiplayer.isPlayerHost()) {
const handleEndGameMessagesReceived = () => {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
// Only other players need to react to the end game message.
return;
}
@@ -2348,6 +2391,50 @@ namespace gdjs {
gdjs.multiplayer.handleLobbyGameEnded();
};
const resumeGameMessageName = '#resumeGame';
const createResumeGameMessage = (): {
messageName: string;
messageData: any;
} => {
return {
messageName: resumeGameMessageName,
messageData: {},
};
};
const sendResumeGameMessage = () => {
// Only the host can inform others that the game is resuming.
if (!gdjs.multiplayer.isCurrentPlayerHost()) {
return;
}
debugLogger.info(`Sending resumeGame message.`);
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const { messageName, messageData } = createResumeGameMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
};
const handleResumeGameMessagesReceived = (
runtimeScene: gdjs.RuntimeScene
) => {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
// Only other players need to react to resume game message.
return;
}
const p2pMessagesMap = gdjs.multiplayerPeerJsHelper.getAllMessagesMap();
const resumeGameMessagesList = p2pMessagesMap.get(resumeGameMessageName);
if (!resumeGameMessagesList) {
return; // No resume game message received.
}
const messages = resumeGameMessagesList.getMessages();
if (!messages.length) return; // No messages to process.
logger.info(`Received resumeGame message.`);
gdjs.multiplayer.resumeGame(runtimeScene);
};
const clearAllMessagesTempData = () => {
_playersLastRoundTripTimes = {};
_playersInfo = {};
@@ -2409,6 +2496,7 @@ namespace gdjs {
// Heartbeats.
handleHeartbeatsToSend,
handleHeartbeatsReceived,
hasReceivedHeartbeatFromPlayer,
// Pings & usernames.
getPlayerPing,
getCurrentPlayerPing,
@@ -2419,12 +2507,14 @@ namespace gdjs {
getConnectedPlayers,
getNumberOfConnectedPlayers,
isPlayerConnected,
getPlayersInfo,
// Leaving players.
hasAnyPlayerJustLeft,
hasPlayerJustLeft,
getPlayersWhoJustLeft,
getLatestPlayerWhoJustLeft,
removePlayerWhoJustLeft,
markPlayerAsDisconnected,
// Joining players.
hasAnyPlayerJustJoined,
hasPlayerJustJoined,
@@ -2433,8 +2523,11 @@ namespace gdjs {
removePlayerWhoJustJoined,
// End game.
sendEndGameMessage,
handleEndGameMessages,
handleEndGameMessagesReceived,
clearAllMessagesTempData,
// Resume game after migration.
sendResumeGameMessage,
handleResumeGameMessagesReceived,
};
};

View File

@@ -398,11 +398,12 @@ namespace gdjs {
export const displayErrorNotification = function (
runtimeScene: gdjs.RuntimeScene
) {
showNotification(
showNotification({
runtimeScene,
'An error occurred while displaying the game lobbies, please try again.',
'error'
);
content:
'An error occurred while displaying the game lobbies, please try again.',
type: 'error',
});
};
/**
@@ -412,7 +413,11 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
playerName: string
) {
showNotification(runtimeScene, `${playerName} left.`, 'warning');
showNotification({
runtimeScene,
content: `${playerName} left.`,
type: 'warning',
});
};
/**
@@ -422,7 +427,11 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
playerName: string
) {
showNotification(runtimeScene, `${playerName} joined.`, 'success');
showNotification({
runtimeScene,
content: `${playerName} joined.`,
type: 'success',
});
};
/**
@@ -431,11 +440,48 @@ namespace gdjs {
export const displayConnectionErrorNotification = function (
runtimeScene: gdjs.RuntimeScene
) {
showNotification(
showNotification({
runtimeScene,
'Could not connect to other players.',
'error'
);
content: 'Could not connect to other players.',
type: 'error',
});
};
/**
* Create, display, and hide a notification when a player leaves the game.
*/
export const displayHostMigrationNotification = function (
runtimeScene: gdjs.RuntimeScene
) {
showNotification({
runtimeScene,
content: `Migrating host...`,
type: 'warning',
id: 'migrating-host',
persist: true,
});
};
export const showHostMigrationFinishedNotification = function (
runtimeScene: gdjs.RuntimeScene
) {
removeNotificationAndShiftOthers('migrating-host');
showNotification({
runtimeScene,
content: `Host migrated!`,
type: 'success',
});
};
export const showHostMigrationFailedNotification = function (
runtimeScene: gdjs.RuntimeScene
) {
removeNotificationAndShiftOthers('migrating-host');
showNotification({
runtimeScene,
content: `Host migration failed.`,
type: 'error',
});
};
const removeNotificationAndShiftOthers = function (
@@ -443,7 +489,9 @@ namespace gdjs {
) {
const notification = document.getElementById(notificationContainerId);
if (!notification) {
logger.error('Notification not found.');
logger.warn(
`Notification ${notificationContainerId} not found. skipping`
);
return;
}
const index = notificationContainerIds.indexOf(notificationContainerId);
@@ -452,8 +500,8 @@ namespace gdjs {
}
notification.remove();
// Shift the other notifications up.
for (let i = 0; i < notificationContainerIds.length; i++) {
// Shift the notifications that are below the one that was removed up.
for (let i = index; i < notificationContainerIds.length; i++) {
const notification = document.getElementById(
notificationContainerIds[i]
);
@@ -468,11 +516,19 @@ namespace gdjs {
/**
* Helper to show a notification to the user, that disappears automatically.
*/
export const showNotification = function (
runtimeScene: gdjs.RuntimeScene,
content: string,
type: 'success' | 'warning' | 'error'
) {
export const showNotification = function ({
runtimeScene,
content,
type,
id,
persist,
}: {
runtimeScene: gdjs.RuntimeScene;
content: string;
type: 'success' | 'warning' | 'error';
id?: string;
persist?: boolean;
}) {
// When we show a notification, we add it below the other ones.
// We also remove the oldest one if there are too many > 5.
if (notificationContainerIds.length > 5) {
@@ -486,7 +542,8 @@ namespace gdjs {
}
// We generate a random ID for the notification, so they can stack.
const id = `notification-${Math.random().toString(36).substring(7)}`;
const notificationId =
id || `notification-${Math.random().toString(36).substring(7)}`;
const domContainer = runtimeScene
.getGame()
@@ -498,7 +555,7 @@ namespace gdjs {
}
const divContainer = document.createElement('div');
divContainer.id = id;
divContainer.id = notificationId;
divContainer.style.position = 'absolute';
divContainer.style.pointerEvents = 'all';
divContainer.style.backgroundColor =
@@ -544,10 +601,14 @@ namespace gdjs {
divContainer.appendChild(loggedText);
domContainer.appendChild(divContainer);
notificationContainerIds.push(id);
notificationContainerIds.push(notificationId);
if (persist) {
return;
}
const animationTime = 700;
const notificationTime = 5000;
const notificationTime = 3000;
setTimeout(() => {
try {
divContainer.animate(
@@ -566,7 +627,7 @@ namespace gdjs {
}, notificationTime);
// Use timeout because onanimationend listener does not work.
setTimeout(() => {
removeNotificationAndShiftOthers(id);
removeNotificationAndShiftOthers(notificationId);
}, notificationTime + animationTime);
};

View File

@@ -24,48 +24,46 @@ namespace gdjs {
actionOnPlayerDisconnect: string;
// The last time the object has been synchronized.
// This is to avoid synchronizing the object too often, see _objectMaxTickRate.
// This is to avoid synchronizing the object too often, see _objectMaxSyncRate.
_lastObjectSyncTimestamp: number = 0;
// The number of times per second the object should be synchronized if it keeps changing.
_objectMaxTickRate: number = 60;
// The last time the basic object info has been synchronized.
_lastBasicObjectSyncTimestamp: number = 0;
// The number of times per second the object basic info should be synchronized when it doesn't change.
_objectBasicInfoTickRate: number = 5;
_objectBasicInfoSyncRate: number = 5;
// The last data sent to synchronize the basic info of the object.
_lastSentBasicObjectSyncData: BasicObjectNetworkSyncData | undefined;
// When we know that the basic info of the object has been updated, we can force sending them
// on the max tickrate for a number of times to ensure they are received, without the need of an acknowledgment.
// on the max SyncRate for a number of times to ensure they are received, without the need of an acknowledgment.
_numberOfForcedBasicObjectUpdates: number = 0;
// The last time the variables have been synchronized.
_lastVariablesSyncTimestamp: number = 0;
// The number of times per second the variables should be synchronized.
_variablesTickRate: number = 1;
_variablesSyncRate: number = 1;
// The last data sent to synchronize the variables.
_lastSentVariableSyncData: VariableNetworkSyncData[] | undefined;
// When we know that the variables have been updated, we can force sending them
// on the same tickrate as the object update for a number of times
// on the same syncRate as the object update for a number of times
// to ensure they are received, without the need of an acknowledgment.
_numberOfForcedVariablesUpdates: number = 0;
// The last time the effects have been synchronized.
_lastEffectsSyncTimestamp: number = 0;
// The number of times per second the effects should be synchronized.
_effectsTickRate: number = 1;
_effectsSyncRate: number = 1;
// The last data sent to synchronize the effects.
_lastSentEffectSyncData:
| { [effectName: string]: EffectNetworkSyncData }
| undefined;
// When we know that the effects have been updated, we can force sending them
// on the same tickrate as the object update for a number of times
// on the same syncRate as the object update for a number of times
// to ensure they are received, without the need of an acknowledgment.
_numberOfForcedEffectsUpdates: number = 0;
// To avoid seeing too many logs.
_lastLogTimestamp: number = 0;
_logTickRate: number = 1;
_logSyncRate: number = 1;
// Clock to be incremented every time we send a message, to ensure they are ordered
// and old messages are ignored.
_clock: number = 0;
@@ -131,35 +129,35 @@ namespace gdjs {
}
private _hasObjectBeenSyncedWithinMaxRate() {
const objectMaxSyncRate = gdjs.multiplayer.getObjectsSynchronizationRate();
return (
getTimeNow() - this._lastObjectSyncTimestamp <
1000 / this._objectMaxTickRate
getTimeNow() - this._lastObjectSyncTimestamp < 1000 / objectMaxSyncRate
);
}
private _hasObjectBasicInfoBeenSyncedRecently() {
return (
getTimeNow() - this._lastBasicObjectSyncTimestamp <
1000 / this._objectBasicInfoTickRate
1000 / this._objectBasicInfoSyncRate
);
}
private _haveVariablesBeenSyncedRecently() {
return (
getTimeNow() - this._lastVariablesSyncTimestamp <
1000 / this._variablesTickRate
1000 / this._variablesSyncRate
);
}
private _haveEffectsBeenSyncedRecently() {
return (
getTimeNow() - this._lastEffectsSyncTimestamp <
1000 / this._effectsTickRate
1000 / this._effectsSyncRate
);
}
// private _logToConsoleWithThrottle(message: string) {
// if (getTimeNow() - this._lastLogTimestamp > 1000 / this._logTickRate) {
// if (getTimeNow() - this._lastLogTimestamp > 1000 / this._logSyncRate) {
// logger.info(message);
// this._lastLogTimestamp = getTimeNow();
// }
@@ -415,7 +413,10 @@ namespace gdjs {
// For destruction of objects, we allow the host to destroy the object even if it is not the owner.
// This is particularly helpful when a player disconnects, so the host can destroy the object they were owning.
if (!this._isOwnerAsPlayerOrHost() && !gdjs.multiplayer.isPlayerHost()) {
if (
!this._isOwnerAsPlayerOrHost() &&
!gdjs.multiplayer.isCurrentPlayerHost()
) {
return;
}

View File

@@ -1,6 +1,82 @@
namespace gdjs {
const logger = new gdjs.Logger('Multiplayer');
type LobbyChangeHostRequest = {
lobbyId: string;
gameId: string;
peerId: string;
playerId: string;
ping: number;
createdAt: number;
ttl: number;
newLobbyId?: string;
newHostPeerId?: string;
newPlayers?: {
playerNumber: number;
playerId: string;
}[];
};
const getTimeNow =
window.performance && typeof window.performance.now === 'function'
? window.performance.now.bind(window.performance)
: Date.now;
const fetchAsPlayer = async ({
relativeUrl,
method,
body,
dev,
}: {
relativeUrl: string;
method: 'GET' | 'POST';
body?: string;
dev: boolean;
}) => {
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!playerId || !playerToken) {
logger.warn('Cannot fetch as a player if the player is not connected.');
throw new Error(
'Cannot fetch as a player if the player is not connected.'
);
}
const rootApi = dev
? 'https://api-dev.gdevelop.io'
: 'https://api.gdevelop.io';
const url = new URL(`${rootApi}${relativeUrl}`);
url.searchParams.set('playerId', playerId);
const formattedUrl = url.toString();
const headers = {
'Content-Type': 'application/json',
Authorization: `player-game-token ${playerToken}`,
};
const response = await fetch(formattedUrl, {
method,
headers,
body,
});
if (!response.ok) {
throw new Error(
`Error while fetching as a player: ${response.status} ${response.statusText}`
);
}
// Response can either be 'OK' or a JSON object. Get the content before trying to parse it.
const responseText = await response.text();
if (responseText === 'OK') {
return;
}
try {
return JSON.parse(responseText);
} catch (error) {
throw new Error(`Error while parsing the response: ${error}`);
}
};
export namespace multiplayer {
/** Set to true in testing to avoid relying on the multiplayer extension. */
export let disableMultiplayerForTesting = false;
@@ -17,20 +93,38 @@ namespace gdjs {
let _lobbyId: string | null = null;
let _connectionId: string | null = null;
let _shouldEndLobbyWhenHostLeaves = false;
let _lobbyChangeHostRequest: LobbyChangeHostRequest | null = null;
let _lobbyChangeHostRequestInitiatedAt: number | null = null;
let _isChangingHost = false;
let _lobbyNewHostPickedAt: number | null = null;
// Communication methods.
let _lobbiesMessageCallback: ((event: MessageEvent) => void) | null = null;
let _websocket: WebSocket | null = null;
let _websocketHeartbeatInterval: NodeJS.Timeout | null = null;
let _lobbyHeartbeatInterval: NodeJS.Timeout | null = null;
let _websocketHeartbeatIntervalFunction: NodeJS.Timeout | null = null;
let _lobbyHeartbeatIntervalFunction: NodeJS.Timeout | null = null;
const DEFAULT_WEBSOCKET_HEARTBEAT_INTERVAL = 10000;
const DEFAULT_LOBBY_HEARTBEAT_INTERVAL = 30000;
const DEFAULT_COUNTDOWN_SECONDS_TO_START = 5;
let currentLobbyHeartbeatInterval = DEFAULT_LOBBY_HEARTBEAT_INTERVAL;
const DEFAULT_LOBBY_CHANGE_HOST_REQUEST_CHECK_INTERVAL = 1000;
// 10 seconds to be safe, but the backend will answer in less.
const DEFAULT_LOBBY_CHANGE_HOST_REQUEST_TIMEOUT = 10000;
const DEFAULT_LOBBY_EXPECTED_CONNECTED_PLAYERS_CHECK_INTERVAL = 1000;
const DEFAULT_LOBBY_EXPECTED_CONNECTED_PLAYERS_TIMEOUT = 10000;
let _resumeTimeout: NodeJS.Timeout | null = null;
const DEFAULT_LOBBY_EXPECTED_RESUME_TIMEOUT = 12000;
export const DEFAULT_OBJECT_MAX_SYNC_RATE = 30;
// The number of times per second an object should be synchronized if it keeps changing.
export let _objectMaxSyncRate = DEFAULT_OBJECT_MAX_SYNC_RATE;
// Save if we are on dev environment so we don't need to use the runtimeGame every time.
let isUsingGDevelopDevelopmentEnvironment = false;
export let playerNumber: number | null = null;
export let hostPeerId: string | null = null;
gdjs.registerRuntimeScenePreEventsCallback(
(runtimeScene: gdjs.RuntimeScene) => {
@@ -88,6 +182,11 @@ namespace gdjs {
// Then look at the heartbeats received to know if a new player has joined/left.
gdjs.multiplayerMessageManager.handleHeartbeatsReceived();
gdjs.multiplayerMessageManager.handleEndGameMessagesReceived();
gdjs.multiplayerMessageManager.handleResumeGameMessagesReceived(
runtimeScene
);
gdjs.multiplayerMessageManager.handleDestroyInstanceMessagesReceived(
runtimeScene
);
@@ -161,6 +260,19 @@ namespace gdjs {
return url.toString();
};
export const setObjectsSynchronizationRate = (rate: number) => {
if (rate < 1 || rate > 60) {
logger.warn(
`Invalid rate ${rate} for object synchronization. Defaulting to ${DEFAULT_OBJECT_MAX_SYNC_RATE}.`
);
_objectMaxSyncRate = DEFAULT_OBJECT_MAX_SYNC_RATE;
} else {
_objectMaxSyncRate = rate;
}
};
export const getObjectsSynchronizationRate = () => _objectMaxSyncRate;
/**
* Returns true if the game has just started,
* useful to switch to the game scene.
@@ -181,7 +293,7 @@ namespace gdjs {
/**
* Returns the number of players in the lobby.
*/
export const getPlayersInLobbyCount = () => {
export const getPlayersInLobbyCount = (): number => {
// Whether the lobby game has started or not, the number of players in the lobby
// is the number of connected players.
return gdjs.multiplayerMessageManager.getNumberOfConnectedPlayers();
@@ -190,7 +302,7 @@ namespace gdjs {
/**
* Returns true if the player at this position is connected to the lobby.
*/
export const isPlayerConnected = (playerNumber: number) => {
export const isPlayerConnected = (playerNumber: number): boolean => {
return gdjs.multiplayerMessageManager.isPlayerConnected(playerNumber);
};
@@ -199,29 +311,52 @@ namespace gdjs {
* Return 0 if the player is not in the lobby.
* Returns 1, 2, 3, ... if the player is in the lobby.
*/
export const getCurrentPlayerNumber = () => {
export const getCurrentPlayerNumber = (): number => {
return playerNumber || 0;
};
/**
* Returns true if the player is the host in the lobby. Here, player 1.
* Returns true if the player is the host in the lobby.
* This can change during the game.
*/
export const isPlayerHost = () => {
return playerNumber === 1;
export const isCurrentPlayerHost = (): boolean => {
return (
!!hostPeerId &&
hostPeerId === gdjs.multiplayerPeerJsHelper.getCurrentId()
);
};
/**
* Returns true if the host left and the game is either:
* - picking a new host
* - waiting for everyone to connect to the new host
*/
export const isMigratingHost = (): boolean => {
return !!_isChangingHost;
};
/**
* If this is set, instead of migrating the host, the lobby will end when the host leaves.
*/
export const endLobbyWhenHostLeaves = (enable: boolean) => {
_shouldEndLobbyWhenHostLeaves = enable;
};
export const shouldEndLobbyWhenHostLeaves = () =>
_shouldEndLobbyWhenHostLeaves;
/**
* Returns the player username at the given number in the lobby.
* The number is shifted by one, so that the first player has number 1.
*/
export const getPlayerUsername = (playerNumber: number) => {
export const getPlayerUsername = (playerNumber: number): string => {
return gdjs.multiplayerMessageManager.getPlayerUsername(playerNumber);
};
/**
* Returns the player username of the current player in the lobby.
*/
export const getCurrentPlayerUsername = () => {
export const getCurrentPlayerUsername = (): string => {
const currentPlayerNumber = getCurrentPlayerNumber();
return getPlayerUsername(currentPlayerNumber);
};
@@ -241,7 +376,12 @@ namespace gdjs {
// When a player leaves, we send a heartbeat to the backend so that they're aware of the players in the lobby.
// Do not await as we want don't want to block the execution of the of the rest of the logic.
sendHeartbeatToBackend();
if (
isCurrentPlayerHost() &&
isReadyToSendOrReceiveGameUpdateMessages()
) {
sendHeartbeatToBackend();
}
}
};
@@ -253,6 +393,15 @@ namespace gdjs {
runtimeScene,
playerUsername
);
// We also send a heartbeat to the backend right away, so that they're aware of the players in the lobby.
// Do not await as we want don't want to block the execution of the of the rest of the logic.
if (
isCurrentPlayerHost() &&
isReadyToSendOrReceiveGameUpdateMessages()
) {
sendHeartbeatToBackend();
}
}
// We remove the players who just joined 1 by 1, so that they can be treated in different frames.
// This is especially important if the expression to know the latest player who just joined is used,
@@ -310,6 +459,7 @@ namespace gdjs {
_websocket.close();
_connectionId = null;
playerNumber = null;
hostPeerId = null;
_lobbyId = null;
_websocket = null;
}
@@ -339,7 +489,7 @@ namespace gdjs {
_websocket.onopen = () => {
logger.info('Connected to the lobby.');
// Register a heartbeat to keep the connection alive.
_websocketHeartbeatInterval = setInterval(() => {
_websocketHeartbeatIntervalFunction = setInterval(() => {
if (_websocket) {
_websocket.send(
JSON.stringify({
@@ -413,23 +563,21 @@ namespace gdjs {
case 'gameCountdownStarted': {
const messageData = messageContent.data;
const compressionMethod = messageData.compressionMethod || 'none';
const secondsToStart =
messageData.secondsToStart ||
DEFAULT_COUNTDOWN_SECONDS_TO_START;
handleGameCountdownStartedEvent({
runtimeScene,
compressionMethod,
secondsToStart,
});
break;
}
case 'gameStarted': {
const messageData = messageContent.data;
const heartbeatInterval =
currentLobbyHeartbeatInterval =
messageData.heartbeatInterval ||
DEFAULT_LOBBY_HEARTBEAT_INTERVAL;
handleGameStartedEvent({ runtimeScene, heartbeatInterval });
handleGameStartedEvent({
runtimeScene,
});
break;
}
case 'peerId': {
@@ -452,14 +600,14 @@ namespace gdjs {
}
};
_websocket.onclose = () => {
logger.info(
'Disconnected from the lobby. Either manually or game started.'
);
if (!_isLobbyGameRunning) {
logger.info('Disconnected from the lobby.');
}
_connectionId = null;
_websocket = null;
if (_websocketHeartbeatInterval) {
clearInterval(_websocketHeartbeatInterval);
if (_websocketHeartbeatIntervalFunction) {
clearInterval(_websocketHeartbeatIntervalFunction);
}
// If the game is running, then all good.
@@ -576,6 +724,7 @@ namespace gdjs {
}
_connectionId = null;
playerNumber = null;
hostPeerId = null;
_lobbyId = null;
_websocket = null;
};
@@ -613,15 +762,14 @@ namespace gdjs {
const handleGameCountdownStartedEvent = function ({
runtimeScene,
compressionMethod,
secondsToStart,
}: {
runtimeScene: gdjs.RuntimeScene;
compressionMethod: gdjs.multiplayerPeerJsHelper.CompressionMethod;
secondsToStart: number;
}) {
gdjs.multiplayerPeerJsHelper.setCompressionMethod(compressionMethod);
// When the countdown starts, if we are player number 1, then send the peerId to others so they can connect via P2P.
// When the countdown starts, if we are player number 1, we are chosen as the host.
// We then send the peerId to others so they can connect via P2P.
if (getCurrentPlayerNumber() === 1) {
sendPeerId();
}
@@ -639,7 +787,6 @@ namespace gdjs {
lobbiesIframe.contentWindow.postMessage(
{
id: 'gameCountdownStarted',
secondsToStart,
},
'*' // We could restrict to GDevelop games platform but it's not necessary as the message is not sensitive, and it allows easy debugging.
);
@@ -652,43 +799,34 @@ namespace gdjs {
const sendHeartbeatToBackend = async function () {
const gameId = gdjs.projectData.properties.projectUuid;
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!gameId || !playerId || !playerToken || !_lobbyId) {
if (!gameId || !_lobbyId) {
logger.error(
'Cannot keep the lobby playing without the game ID or player ID.'
'Cannot keep the lobby playing without the game ID or lobby ID.'
);
return;
}
const rootApi = isUsingGDevelopDevelopmentEnvironment
? 'https://api-dev.gdevelop.io'
: 'https://api.gdevelop.io';
const headers = {
'Content-Type': 'application/json',
};
let heartbeatUrl = `${rootApi}/play/game/${gameId}/public-lobby/${_lobbyId}/action/heartbeat`;
headers['Authorization'] = `player-game-token ${playerToken}`;
heartbeatUrl += `?playerId=${playerId}`;
const heartbeatRelativeUrl = `/play/game/${gameId}/public-lobby/${_lobbyId}/action/heartbeat`;
const players = gdjs.multiplayerMessageManager.getConnectedPlayers();
try {
await fetch(heartbeatUrl, {
await fetchAsPlayer({
relativeUrl: heartbeatRelativeUrl,
method: 'POST',
headers,
body: JSON.stringify({
players,
}),
dev: isUsingGDevelopDevelopmentEnvironment,
});
} catch (error) {
logger.error('Error while sending heartbeat, retrying:', error);
try {
await fetch(heartbeatUrl, {
await fetchAsPlayer({
relativeUrl: heartbeatRelativeUrl,
method: 'POST',
headers,
body: JSON.stringify({
players,
}),
dev: isUsingGDevelopDevelopmentEnvironment,
});
} catch (error) {
logger.error(
@@ -705,16 +843,14 @@ namespace gdjs {
*/
const handleGameStartedEvent = function ({
runtimeScene,
heartbeatInterval,
}: {
runtimeScene: gdjs.RuntimeScene;
heartbeatInterval: number;
}) {
// It is possible the connection to other players didn't work.
// If that's the case, show an error message and leave the lobby.
// If we are the host, still start the game, as this allows a player to test the game alone.
const allConnectedPeers = gdjs.multiplayerPeerJsHelper.getAllPeers();
if (!isPlayerHost() && allConnectedPeers.length === 0) {
if (!isCurrentPlayerHost() && allConnectedPeers.length === 0) {
gdjs.multiplayerComponents.displayConnectionErrorNotification(
runtimeScene
);
@@ -726,10 +862,10 @@ namespace gdjs {
}
// If we are the host, start pinging the backend to let it know the lobby is running.
if (isPlayerHost()) {
_lobbyHeartbeatInterval = setInterval(async () => {
if (isCurrentPlayerHost()) {
_lobbyHeartbeatIntervalFunction = setInterval(async () => {
await sendHeartbeatToBackend();
}, heartbeatInterval);
}, currentLobbyHeartbeatInterval);
}
// If we are connected to players, then the game can start.
@@ -757,9 +893,11 @@ namespace gdjs {
_isLobbyGameRunning = false;
_lobbyId = null;
playerNumber = null;
hostPeerId = null;
_isReadyToSendOrReceiveGameUpdateMessages = false;
if (_lobbyHeartbeatInterval) {
clearInterval(_lobbyHeartbeatInterval);
if (_lobbyHeartbeatIntervalFunction) {
clearInterval(_lobbyHeartbeatIntervalFunction);
_lobbyHeartbeatIntervalFunction = null;
}
// Disconnect from any P2P connections.
@@ -795,6 +933,7 @@ namespace gdjs {
return;
}
hostPeerId = peerId;
gdjs.multiplayerPeerJsHelper.connect(peerId);
};
@@ -878,10 +1017,315 @@ namespace gdjs {
action: 'updateConnection',
connectionType: 'lobby',
status: 'connected',
peerId: gdjs.multiplayerPeerJsHelper.getCurrentId(),
})
);
};
const clearChangeHostRequestData = function (
runtimeScene: gdjs.RuntimeScene
) {
_lobbyChangeHostRequest = null;
_lobbyChangeHostRequestInitiatedAt = null;
_lobbyNewHostPickedAt = null;
if (_resumeTimeout) {
clearTimeout(_resumeTimeout);
_resumeTimeout = null;
}
_isChangingHost = false;
if (hostPeerId) {
gdjs.multiplayerComponents.showHostMigrationFinishedNotification(
runtimeScene
);
} else {
gdjs.multiplayerComponents.showHostMigrationFailedNotification(
runtimeScene
);
}
};
export const resumeGame = async function (runtimeScene: gdjs.RuntimeScene) {
if (isCurrentPlayerHost()) {
// Send message to other players to indicate the game is resuming.
gdjs.multiplayerMessageManager.sendResumeGameMessage();
// Start sending heartbeats to the backend.
await sendHeartbeatToBackend();
_lobbyHeartbeatIntervalFunction = setInterval(async () => {
await sendHeartbeatToBackend();
}, currentLobbyHeartbeatInterval);
}
// Migration is finished.
clearChangeHostRequestData(runtimeScene);
};
/**
* When a host is being changed, multiple cases can happen:
* - We are the new host and the only one in the lobby. Unpause the game right away.
* - We are the new host and there are other players in the new lobby. Wait for them to connect:
* - if they are all connected, unpause the game.
* - if we reach a timeout, a player may have disconnected at the same time, unpause the game.
* - We are not the new host. Connect to the new host peerId.
* - If we cannot connect, leave the lobby.
* - when we receive a message to unpause the game, unpause it.
* - if we reach a timeout without the message, leave the lobby, something wrong happened.
*/
const checkHostChangeRequestRegularly = async function ({
runtimeScene,
}: {
runtimeScene: gdjs.RuntimeScene;
}) {
if (!_lobbyChangeHostRequest || !_lobbyChangeHostRequestInitiatedAt) {
return;
}
// Refresh the request to get the latest information.
try {
const changeHostRelativeUrl = `/play/game/${
_lobbyChangeHostRequest.gameId
}/public-lobby/${
_lobbyChangeHostRequest.lobbyId
}/lobby-change-host-request?peerId=${gdjs.multiplayerPeerJsHelper.getCurrentId()}`;
const lobbyChangeHostRequest = await fetchAsPlayer({
relativeUrl: changeHostRelativeUrl,
method: 'GET',
dev: isUsingGDevelopDevelopmentEnvironment,
});
_lobbyChangeHostRequest = lobbyChangeHostRequest;
} catch (error) {
logger.error(
'Error while trying to retrieve the lobby change host request:',
error
);
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
return;
}
if (!_lobbyChangeHostRequest) {
throw new Error('No lobby change host request received.');
}
const newHostPeerId = _lobbyChangeHostRequest.newHostPeerId;
if (!newHostPeerId) {
logger.info('No new host picked yet.');
if (
getTimeNow() - _lobbyChangeHostRequestInitiatedAt >
DEFAULT_LOBBY_CHANGE_HOST_REQUEST_TIMEOUT
) {
logger.error(
'Timeout while waiting for the lobby host change. Giving up.'
);
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
return;
}
logger.info('Retrying...');
setTimeout(() => {
checkHostChangeRequestRegularly({ runtimeScene });
}, DEFAULT_LOBBY_CHANGE_HOST_REQUEST_CHECK_INTERVAL);
return;
}
try {
const newLobbyId = _lobbyChangeHostRequest.newLobbyId;
const newPlayers = _lobbyChangeHostRequest.newPlayers;
if (!newLobbyId || !newPlayers) {
logger.error(
'Change host request is incomplete. Cannot change host.'
);
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
return;
}
hostPeerId = newHostPeerId;
_lobbyNewHostPickedAt = getTimeNow();
_lobbyId = newLobbyId;
if (newHostPeerId === gdjs.multiplayerPeerJsHelper.getCurrentId()) {
logger.info(
`We are the new host. Switching to lobby ${newLobbyId} and awaiting for ${
newPlayers.length - 1
} player(s) to connect.`
);
await checkExpectedConnectedPlayersRegularly({
runtimeScene,
});
} else {
logger.info(
`Connecting to new host and switching lobby to ${newLobbyId}.`
);
gdjs.multiplayerPeerJsHelper.connect(newHostPeerId);
_resumeTimeout = setTimeout(() => {
logger.error(
'Timeout while waiting for the game to resume. Leaving the lobby.'
);
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
}, DEFAULT_LOBBY_EXPECTED_RESUME_TIMEOUT);
}
} catch (error) {
logger.error('Error while trying to change host:', error);
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
}
};
/**
* Helper for the new host, to check if they have all the expected players connected.
*/
const checkExpectedConnectedPlayersRegularly = async function ({
runtimeScene,
}: {
runtimeScene: gdjs.RuntimeScene;
}) {
if (!_lobbyChangeHostRequest) {
return;
}
const expectedNewPlayers = _lobbyChangeHostRequest.newPlayers;
if (!expectedNewPlayers) {
logger.error('No expected players in the lobby change host request.');
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
return;
}
const expectedNewOtherPlayerNumbers = expectedNewPlayers.map(
(player) => player.playerNumber
);
// First look for players who left during the migration.
const playerNumbersConnectedBeforeMigration = gdjs.multiplayerMessageManager
.getConnectedPlayers()
.map((player) => player.playerNumber);
const playerNumbersWhoLeftDuringMigration = playerNumbersConnectedBeforeMigration.filter(
(playerNumberBeforeMigration) =>
!expectedNewOtherPlayerNumbers.includes(playerNumberBeforeMigration)
);
playerNumbersWhoLeftDuringMigration.map((playerNumberWhoLeft) => {
logger.info(
`Player ${playerNumberWhoLeft} left during the host migration. Marking as disconnected.`
);
gdjs.multiplayerMessageManager.markPlayerAsDisconnected({
runtimeScene,
playerNumber: playerNumberWhoLeft,
});
});
// Then check if all expected players are connected.
const playerNumbersWhoDidNotConnect = expectedNewOtherPlayerNumbers.filter(
(otherPlayerNumber) =>
otherPlayerNumber !== playerNumber && // We don't look for ourselves
!gdjs.multiplayerMessageManager.hasReceivedHeartbeatFromPlayer(
otherPlayerNumber
)
);
if (playerNumbersWhoDidNotConnect.length === 0) {
logger.info('All expected players are connected. Resuming the game.');
await resumeGame(runtimeScene);
return;
}
if (
_lobbyNewHostPickedAt &&
getTimeNow() - _lobbyNewHostPickedAt >
DEFAULT_LOBBY_EXPECTED_CONNECTED_PLAYERS_TIMEOUT &&
playerNumbersWhoDidNotConnect.length > 0
) {
logger.error(
`Timeout while waiting for players ${playerNumbersWhoDidNotConnect.join(
', '
)} to connect. Assume they disconnected.`
);
playerNumbersWhoDidNotConnect.map((missingPlayerNumber) => {
gdjs.multiplayerMessageManager.markPlayerAsDisconnected({
runtimeScene,
playerNumber: missingPlayerNumber,
});
});
await resumeGame(runtimeScene);
return;
}
setTimeout(() => {
checkExpectedConnectedPlayersRegularly({
runtimeScene,
});
}, DEFAULT_LOBBY_EXPECTED_CONNECTED_PLAYERS_CHECK_INTERVAL);
};
/**
* When the host disconnects, we inform the backend we lost the connection and we need a new lobby/host.
*/
export const handleHostDisconnected = async function ({
runtimeScene,
}: {
runtimeScene: gdjs.RuntimeScene;
}) {
if (!_isLobbyGameRunning) {
// This can happen when the game ends. Nothing to do here.
return;
}
if (_lobbyChangeHostRequest) {
// The new host disconnected while we are already changing host.
// Let's end the lobby game to avoid weird situations.
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
}
const gameId = gdjs.projectData.properties.projectUuid;
if (!gameId || !_lobbyId) {
logger.error(
'Cannot ask for a host change without the game ID or lobby ID.'
);
return;
}
try {
_isChangingHost = true;
gdjs.multiplayerComponents.displayHostMigrationNotification(
runtimeScene
);
const changeHostRelativeUrl = `/play/game/${gameId}/public-lobby/${_lobbyId}/lobby-change-host-request`;
const playersInfo = gdjs.multiplayerMessageManager.getPlayersInfo();
const playersInfoForHostChange = Object.keys(playersInfo).map(
(playerNumber) => {
return {
playerNumber: parseInt(playerNumber, 10),
playerId: playersInfo[playerNumber].playerId,
ping: playersInfo[playerNumber].ping,
};
}
);
const body = JSON.stringify({
playersInfo: playersInfoForHostChange,
peerId: gdjs.multiplayerPeerJsHelper.getCurrentId(),
});
const lobbyChangeHostRequest = await fetchAsPlayer({
relativeUrl: changeHostRelativeUrl,
method: 'POST',
body,
dev: isUsingGDevelopDevelopmentEnvironment,
});
_lobbyChangeHostRequest = lobbyChangeHostRequest;
_lobbyChangeHostRequestInitiatedAt = getTimeNow();
await checkHostChangeRequestRegularly({ runtimeScene });
} catch (error) {
logger.error('Error while trying to change host:', error);
handleLobbyGameEnded();
clearChangeHostRequestData(runtimeScene);
}
};
/**
* Action to end the lobby game.
* This will update the lobby status and inform everyone in the lobby that the game has ended.
@@ -891,7 +1335,7 @@ namespace gdjs {
return;
}
if (!isPlayerHost()) {
if (!isCurrentPlayerHost()) {
logger.error('Only the host can end the game.');
return;
}
@@ -906,31 +1350,18 @@ namespace gdjs {
// Also call backend to end the game.
const gameId = gdjs.projectData.properties.projectUuid;
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!gameId || !playerId || !playerToken || !_lobbyId) {
logger.error('Cannot end the lobby without the game ID or player ID.');
if (!gameId || !_lobbyId) {
logger.error('Cannot end the lobby without the game ID or lobby ID.');
return;
}
const rootApi = isUsingGDevelopDevelopmentEnvironment
? 'https://api-dev.gdevelop.io'
: 'https://api.gdevelop.io';
const headers = {
'Content-Type': 'application/json',
};
let endGameUrl = `${rootApi}/play/game/${gameId}/public-lobby/${_lobbyId}/action/end`;
headers['Authorization'] = `player-game-token ${playerToken}`;
endGameUrl += `?playerId=${playerId}`;
const endGameRelativeUrl = `/play/game/${gameId}/public-lobby/${_lobbyId}/action/end`;
try {
await fetch(endGameUrl, {
await fetchAsPlayer({
relativeUrl: endGameRelativeUrl,
method: 'POST',
headers,
body: JSON.stringify({
gameId,
lobbyId: _lobbyId,
}),
body: JSON.stringify({}),
dev: isUsingGDevelopDevelopmentEnvironment,
});
} catch (error) {
logger.error('Error while ending the game:', error);
@@ -966,6 +1397,8 @@ namespace gdjs {
peerId,
})
);
// We are the host.
hostPeerId = peerId;
};
/**

View File

@@ -313,9 +313,14 @@ describe('Multiplayer', () => {
/**
* Helper to fast forward a bit of time in players games, so that heartbeats
* are sent and all players are aware of each other.
* @param {{ playerNumber: number, peerId: string}[]} players
* @param {{ playerNumber: number, peerId: string, isHost?: boolean }[]} players
*/
const initiateGameWithPlayers = (players) => {
// Find the host.
const host = players.find((player) => player.isHost);
if (!host)
throw new Error('No host defined in players, cannot initiate game.');
// Create the instances of the MultiplayerMessageManager and MultiplayerVariablesManager
// for each player.
for (const player of players) {
@@ -325,6 +330,9 @@ describe('Multiplayer', () => {
peerMultiplayerVariablesManager[
player.peerId
] = gdjs.makeMultiplayerVariablesManager();
// Define the host for everyone.
gdjs.multiplayer.hostPeerId = host.peerId;
}
// Use a scene to simulate the game loop moving forward.
@@ -395,12 +403,16 @@ describe('Multiplayer', () => {
gdjs.multiplayer.disableMultiplayerForTesting = false;
gdjs.multiplayer._isLobbyGameRunning = true;
gdjs.multiplayer._isReadyToSendOrReceiveGameUpdateMessages = true;
// Sync as fast as possible for tests.
gdjs.multiplayer._objectMaxSyncRate = Infinity;
});
afterEach(() => {
gdjs.multiplayerPeerJsHelper = _originalP2pIfAny;
gdjs.multiplayer.disableMultiplayerForTesting = true;
gdjs.multiplayer._isLobbyGameRunning = false;
gdjs.multiplayer._isReadyToSendOrReceiveGameUpdateMessages = false;
gdjs.multiplayer._objectMaxSyncRate =
gdjs.multiplayer.DEFAULT_OBJECT_MAX_SYNC_RATE;
});
describe('Single scene tests', () => {
@@ -411,7 +423,7 @@ describe('Multiplayer', () => {
initiateGameWithPlayers,
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -607,7 +619,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -693,7 +705,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -830,7 +842,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1026,7 +1038,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -1081,13 +1093,11 @@ describe('Multiplayer', () => {
const {
object: p1SpriteObject,
behavior: p1SpriteObjectBehavior,
} = getObjectAndMultiplayerBehaviorsFromScene(
p1RuntimeScene,
'MySpriteObject'
)[0];
p1SpriteObjectBehavior._objectMaxTickRate = Infinity;
p1SpriteObject.setX(242);
p1SpriteObject.setY(243);
p1RuntimeScene.renderAndStep(1000 / 60);
@@ -1155,7 +1165,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1237,13 +1247,11 @@ describe('Multiplayer', () => {
const {
object: p2SpriteObject,
behavior: p2SpriteObjectBehavior,
} = getObjectAndMultiplayerBehaviorsFromScene(
p2RuntimeScene,
'MySpriteObject'
)[0];
p2SpriteObjectBehavior._objectMaxTickRate = Infinity;
p2SpriteObject.setX(242);
p2SpriteObject.setY(243);
p2RuntimeScene.renderAndStep(1000 / 60);
@@ -1358,7 +1366,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1630,7 +1638,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1840,7 +1848,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1899,7 +1907,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -1941,7 +1949,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -2140,12 +2148,10 @@ describe('Multiplayer', () => {
const {
object: p2SpriteObject,
behavior: p2SpriteMultiplayerObjectBehavior,
} = getObjectAndMultiplayerBehaviorsFromScene(
p2RuntimeScene,
'MySpriteObject'
)[0];
p2SpriteMultiplayerObjectBehavior._objectMaxTickRate = Infinity;
p2SpriteObject.setX(242);
p2SpriteObject.setY(243);
p2RuntimeScene.renderAndStep(1000 / 60);
@@ -2202,7 +2208,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2365,7 +2371,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2450,7 +2456,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2582,7 +2588,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2676,7 +2682,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -2685,7 +2691,7 @@ describe('Multiplayer', () => {
// Player 2 leaves.
const newConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 3, peerId: 'player-3' },
];
// Host sees the player 2 leaving.
@@ -2712,7 +2718,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 3, peerId: 'player-3' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2723,7 +2729,7 @@ describe('Multiplayer', () => {
// Player 2 joins.
const newConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];

View File

@@ -30,6 +30,102 @@ PanelSpriteObject::PanelSpriteObject()
PanelSpriteObject::~PanelSpriteObject() {}
bool PanelSpriteObject::UpdateProperty(const gd::String& propertyName,
const gd::String& newValue) {
if (propertyName == "texture") {
textureName = newValue;
return true;
}
if (propertyName == "width") {
SetWidth(newValue.To<double>());
return true;
}
if (propertyName == "height") {
SetHeight(newValue.To<double>());
return true;
}
if (propertyName == "leftMargin") {
SetLeftMargin(newValue.To<double>());
return true;
}
if (propertyName == "topMargin") {
SetTopMargin(newValue.To<double>());
return true;
}
if (propertyName == "rightMargin") {
SetRightMargin(newValue.To<double>());
return true;
}
if (propertyName == "bottomMargin") {
SetBottomMargin(newValue.To<double>());
return true;
}
if (propertyName == "tiled") {
SetTiled(newValue == "1");
return true;
}
return false;
}
std::map<gd::String, gd::PropertyDescriptor> PanelSpriteObject::GetProperties() const {
std::map<gd::String, gd::PropertyDescriptor> objectProperties;
objectProperties["texture"]
.SetValue(textureName)
.SetType("resource")
.AddExtraInfo("image")
.SetLabel(_("Texture"));
objectProperties["width"]
.SetValue(gd::String::From(width))
.SetType("number")
.SetLabel(_("Width"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Default size"));
objectProperties["height"]
.SetValue(gd::String::From(height))
.SetType("number")
.SetLabel(_("Height"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Default size"));
objectProperties["leftMargin"]
.SetValue(gd::String::From(leftMargin))
.SetType("number")
.SetLabel(_("Left"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Margins"));
objectProperties["topMargin"]
.SetValue(gd::String::From(topMargin))
.SetType("number")
.SetLabel(_("Top"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Margins"));
objectProperties["rightMargin"]
.SetValue(gd::String::From(rightMargin))
.SetType("number")
.SetLabel(_("Right"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Margins"));
objectProperties["bottomMargin"]
.SetValue(gd::String::From(bottomMargin))
.SetType("number")
.SetLabel(_("Bottom"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Margins"));
objectProperties["tiled"]
.SetValue(tiled ? "true" : "false")
.SetType("boolean")
.SetLabel(_("Repeat borders and center textures (instead of stretching them)"));
return objectProperties;
}
void PanelSpriteObject::DoUnserializeFrom(
gd::Project& project, const gd::SerializerElement& element) {
textureName = element.GetStringAttribute("texture");

View File

@@ -23,12 +23,18 @@ class GD_EXTENSION_API PanelSpriteObject : public gd::ObjectConfiguration {
public:
PanelSpriteObject();
virtual ~PanelSpriteObject();
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const {
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const override {
return std::unique_ptr<gd::ObjectConfiguration>(
new PanelSpriteObject(*this));
}
virtual void ExposeResources(gd::ArbitraryResourceWorker &worker);
virtual void ExposeResources(gd::ArbitraryResourceWorker &worker) override;
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties()
const override;
virtual bool UpdateProperty(const gd::String& name,
const gd::String& value) override;
double GetWidth() const { return width; };
double GetHeight() const { return height; };
@@ -63,15 +69,15 @@ class GD_EXTENSION_API PanelSpriteObject : public gd::ObjectConfiguration {
};
const gd::String &GetTexture() const { return textureName; };
gd::String textureName; ///< deprecated. Use Get/SetTexture instead.
private:
virtual void DoUnserializeFrom(gd::Project &project,
const gd::SerializerElement &element);
const gd::SerializerElement &element) override;
#if defined(GD_IDE_ONLY)
virtual void DoSerializeTo(gd::SerializerElement &element) const;
virtual void DoSerializeTo(gd::SerializerElement &element) const override;
#endif
gd::String textureName;
double width;
double height;

View File

@@ -32,6 +32,10 @@ namespace gdjs {
const StretchedSprite = !tiled ? PIXI.Sprite : PIXI.TilingSprite;
this._spritesContainer = new PIXI.Container();
this._wrapperContainer = new PIXI.Container();
// All these textures are going to be replaced in the call to `setTexture`.
// But to be safe and preserve the invariant that "these objects own their own
// textures", we create a new texture for each sprite.
this._centerSprite = new StretchedSprite(
new PIXI.Texture(texture.baseTexture)
);
@@ -39,21 +43,21 @@ namespace gdjs {
// Right
new StretchedSprite(new PIXI.Texture(texture.baseTexture)),
// Top-Right
new PIXI.Sprite(texture),
new PIXI.Sprite(new PIXI.Texture(texture.baseTexture)),
// Top
new StretchedSprite(new PIXI.Texture(texture.baseTexture)),
// Top-Left
new PIXI.Sprite(texture),
new PIXI.Sprite(new PIXI.Texture(texture.baseTexture)),
// Left
new StretchedSprite(new PIXI.Texture(texture.baseTexture)),
// Bottom-Left
new PIXI.Sprite(texture),
new PIXI.Sprite(new PIXI.Texture(texture.baseTexture)),
// Bottom
new StretchedSprite(new PIXI.Texture(texture.baseTexture)),
new PIXI.Sprite(texture),
// Bottom-Right
new PIXI.Sprite(new PIXI.Texture(texture.baseTexture)),
];
//Bottom-Right
this.setTexture(textureName, instanceContainer);
this._spritesContainer.removeChildren();
this._spritesContainer.addChild(this._centerSprite);
@@ -209,7 +213,6 @@ namespace gdjs {
this._textureHeight = texture.height;
function makeInsideTexture(rect) {
//TODO
if (rect.width < 0) {
rect.width = 0;
}
@@ -236,6 +239,7 @@ namespace gdjs {
}
return rect;
}
this._centerSprite.texture.destroy(false);
this._centerSprite.texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -249,6 +253,7 @@ namespace gdjs {
);
//Top, Bottom, Right, Left borders:
this._borderSprites[0].texture.destroy(false);
this._borderSprites[0].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -260,6 +265,7 @@ namespace gdjs {
)
)
);
this._borderSprites[2].texture.destroy(false);
this._borderSprites[2].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -271,6 +277,7 @@ namespace gdjs {
)
)
);
this._borderSprites[4].texture.destroy(false);
this._borderSprites[4].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -282,6 +289,7 @@ namespace gdjs {
)
)
);
this._borderSprites[6].texture.destroy(false);
this._borderSprites[6].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -293,6 +301,7 @@ namespace gdjs {
)
)
);
this._borderSprites[1].texture.destroy(false);
this._borderSprites[1].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -304,10 +313,12 @@ namespace gdjs {
)
)
);
this._borderSprites[3].texture.destroy(false);
this._borderSprites[3].texture = new PIXI.Texture(
texture,
makeInsideTexture(new PIXI.Rectangle(0, 0, obj._lBorder, obj._tBorder))
);
this._borderSprites[5].texture.destroy(false);
this._borderSprites[5].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -319,6 +330,7 @@ namespace gdjs {
)
)
);
this._borderSprites[7].texture.destroy(false);
this._borderSprites[7].texture = new PIXI.Texture(
texture,
makeInsideTexture(
@@ -395,7 +407,8 @@ namespace gdjs {
}
destroy() {
// Destroy textures because they are instantiated by this class.
// Destroy textures because they are instantiated by this class:
// all textures of borderSprites and centerSprite are "owned" by them.
for (const borderSprite of this._borderSprites) {
borderSprite.destroy({ texture: true });
}

View File

@@ -172,6 +172,9 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
/**

View File

@@ -182,6 +182,7 @@ module.exports = {
.setValue(behaviorContent.getChild('bodyType').getStringValue())
.setType('Choice')
.setLabel('Type')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.addExtraInfo('Static')
.addExtraInfo('Dynamic')
.addExtraInfo('Kinematic');
@@ -190,6 +191,7 @@ module.exports = {
.setValue(
behaviorContent.getChild('bullet').getBoolValue() ? 'true' : 'false'
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setType('Boolean')
.setLabel('Bullet');
behaviorProperties
@@ -199,6 +201,7 @@ module.exports = {
? 'true'
: 'false'
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setType('Boolean')
.setLabel('Fixed Rotation');
behaviorProperties
@@ -206,6 +209,7 @@ module.exports = {
.setValue(
behaviorContent.getChild('canSleep').getBoolValue() ? 'true' : 'false'
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setType('Boolean')
.setLabel('Can Sleep');
behaviorProperties
@@ -213,6 +217,7 @@ module.exports = {
.setValue(behaviorContent.getChild('shape').getStringValue())
.setType('Choice')
.setLabel('Shape')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.addExtraInfo('Box')
.addExtraInfo('Circle')
.addExtraInfo('Edge')
@@ -227,7 +232,8 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Dimension A');
.setLabel('Shape Dimension A')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('shapeDimensionB')
.setValue(
@@ -238,7 +244,8 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Dimension B');
.setLabel('Shape Dimension B')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('shapeOffsetX')
.setValue(
@@ -246,7 +253,8 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Offset X');
.setLabel('Shape Offset X')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('shapeOffsetY')
.setValue(
@@ -254,7 +262,8 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Offset Y');
.setLabel('Shape Offset Y')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('polygonOrigin')
.setValue(
@@ -266,7 +275,8 @@ module.exports = {
.setLabel('Polygon Origin')
.addExtraInfo('Center')
.addExtraInfo('Origin')
.addExtraInfo('TopLeft');
.addExtraInfo('TopLeft')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('vertices')
.setValue(
@@ -274,7 +284,8 @@ module.exports = {
? gd.Serializer.toJSON(behaviorContent.getChild('vertices'))
: '[]'
)
.setLabel('Vertices');
.setLabel('Vertices')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('density')
.setValue(
@@ -315,24 +326,28 @@ module.exports = {
.toString(10)
)
.setType('Number')
.setLabel('Angular Damping');
.setLabel('Angular Damping')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('gravityScale')
.setValue(
behaviorContent.getChild('gravityScale').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Gravity Scale');
.setLabel('Gravity Scale')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('layers')
.setValue(behaviorContent.getChild('layers').getIntValue().toString(10))
.setType('Number')
.setLabel('Layers');
.setLabel('Layers')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
behaviorProperties
.getOrCreate('masks')
.setValue(behaviorContent.getChild('masks').getIntValue().toString(10))
.setType('Number')
.setLabel('Masks');
.setLabel('Masks')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
return behaviorProperties;
};
@@ -379,16 +394,15 @@ module.exports = {
return true;
}
if (propertyName === 'scaleX') {
if (propertyName === 'worldScale') {
const newValueAsNumber = parseInt(newValue, 10);
if (newValueAsNumber !== newValueAsNumber) return false;
if (!sharedContent.hasChild('worldScale')) {
sharedContent.addChild('worldScale');
}
sharedContent.getChild('worldScale').setDoubleValue(newValueAsNumber);
// Set deprecated properties for compatibility with 5.4.209-
sharedContent.getChild('scaleX').setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'scaleY') {
const newValueAsNumber = parseInt(newValue, 10);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent.getChild('scaleY').setDoubleValue(newValueAsNumber);
return true;
}
@@ -412,16 +426,22 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getNewton());
if (!sharedContent.hasChild('worldScale')) {
sharedContent.addChild('worldScale');
sharedContent
.getChild('worldScale')
.setDoubleValue(
Math.sqrt(
sharedContent.getChild('scaleX').getDoubleValue() *
sharedContent.getChild('scaleY').getDoubleValue()
)
);
}
sharedProperties
.getOrCreate('scaleX')
.getOrCreate('worldScale')
.setValue(
sharedContent.getChild('scaleX').getDoubleValue().toString(10)
)
.setType('Number');
sharedProperties
.getOrCreate('scaleY')
.setValue(
sharedContent.getChild('scaleY').getDoubleValue().toString(10)
sharedContent.getChild('worldScale').getDoubleValue().toString(10)
)
.setType('Number');
@@ -430,6 +450,8 @@ module.exports = {
sharedData.initializeContent = function (behaviorContent) {
behaviorContent.addChild('gravityX').setDoubleValue(0);
behaviorContent.addChild('gravityY').setDoubleValue(9.8);
behaviorContent.addChild('worldScale').setDoubleValue(100);
// Set deprecated properties for compatibility with 5.4.209-
behaviorContent.addChild('scaleX').setDoubleValue(100);
behaviorContent.addChild('scaleY').setDoubleValue(100);
};
@@ -457,6 +479,19 @@ module.exports = {
);
// Global
aut
.addExpression(
'WorldScale',
_('World scale'),
_('Return the world scale.'),
_('Global'),
'res/physics32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.getCodeExtraInformation()
.setFunctionName('getWorldScale');
aut
.addCondition(
'GravityX',
@@ -1742,7 +1777,7 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angular impulse (N·m·s'))
.addParameter('expression', _('Angular impulse (N·m·s)'))
.setParameterLongDescription(
_(
'An impulse is like a rotation speed addition but depends on the mass.'
@@ -1769,7 +1804,7 @@ module.exports = {
'Inertia',
_('Inertia'),
_(
'Return the rotational inertia of the object (in kilograms * meters * meters)'
'Return the rotational inertia of the object (in kilograms · meters²)'
),
'',
'res/physics32.png'

View File

@@ -15,6 +15,8 @@ namespace gdjs {
lvy: number | undefined;
av: number | undefined;
aw: boolean | undefined;
layers: number;
masks: number;
}
export interface Physics2NetworkSyncData extends BehaviorNetworkSyncData {
@@ -23,9 +25,15 @@ namespace gdjs {
export class Physics2SharedData {
gravityX: float;
gravityY: float;
worldScale: float;
worldInvScale: float;
/** @deprecated Use `worldScale` instead */
scaleX: float;
/** @deprecated Use `worldScale` instead */
scaleY: float;
/** @deprecated Use `worldInvScale` instead */
invScaleX: float;
/** @deprecated Use `worldInvScale` instead */
invScaleY: float;
timeStep: float;
frameTime: float = 0;
@@ -52,10 +60,13 @@ namespace gdjs {
this._registeredBehaviors = new Set();
this.gravityX = sharedData.gravityX;
this.gravityY = sharedData.gravityY;
this.scaleX = sharedData.scaleX === 0 ? 100 : sharedData.scaleX;
this.scaleY = sharedData.scaleY === 0 ? 100 : sharedData.scaleY;
this.scaleX = sharedData.scaleX || 100;
this.scaleY = sharedData.scaleY || 100;
this.invScaleX = 1 / this.scaleX;
this.invScaleY = 1 / this.scaleY;
this.worldScale =
sharedData.worldScale || Math.sqrt(this.scaleX * this.scaleY);
this.worldInvScale = 1 / this.worldScale;
this.timeStep = 1 / 60;
this.world = new Box2D.b2World(
new Box2D.b2Vec2(this.gravityX, this.gravityY)
@@ -513,6 +524,8 @@ namespace gdjs {
...super.getNetworkSyncData(),
props: {
...bodyProps,
layers: this.layers,
masks: this.masks,
},
};
}
@@ -556,6 +569,14 @@ namespace gdjs {
this._body.SetAwake(behaviorSpecificProps.aw);
}
}
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
}
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
}
}
onDeActivate() {
@@ -642,10 +663,10 @@ namespace gdjs {
createShape(): Box2D.b2FixtureDef {
// Get the scaled offset
const offsetX = this.shapeOffsetX
? this.shapeOffsetX * this.shapeScale * this._sharedData.invScaleX
? this.shapeOffsetX * this.shapeScale * this._sharedData.worldInvScale
: 0;
const offsetY = this.shapeOffsetY
? this.shapeOffsetY * this.shapeScale * this._sharedData.invScaleY
? this.shapeOffsetY * this.shapeScale * this._sharedData.worldInvScale
: 0;
// Generate the base shape
@@ -657,12 +678,14 @@ namespace gdjs {
// Average radius from width and height
if (this.shapeDimensionA > 0) {
shape.set_m_radius(
this.shapeDimensionA * this.shapeScale * this._sharedData.invScaleX
this.shapeDimensionA *
this.shapeScale *
this._sharedData.worldInvScale
);
} else {
const radius =
(this.owner.getWidth() * this._sharedData.invScaleX +
this.owner.getHeight() * this._sharedData.invScaleY) /
(this.owner.getWidth() * this._sharedData.worldInvScale +
this.owner.getHeight() * this._sharedData.worldInvScale) /
4;
shape.set_m_radius(radius > 0 ? radius : 1);
}
@@ -680,10 +703,10 @@ namespace gdjs {
) {
let width =
(this.owner.getWidth() > 0 ? this.owner.getWidth() : 1) *
this._sharedData.invScaleX;
this._sharedData.worldInvScale;
let height =
(this.owner.getHeight() > 0 ? this.owner.getHeight() : 1) *
this._sharedData.invScaleY;
this._sharedData.worldInvScale;
// Set the shape box
shape.SetAsBox(
@@ -728,12 +751,12 @@ namespace gdjs {
Box2D.HEAPF32[(this._verticesBuffer + offset) >> 2] =
(this.polygon.vertices[i][0] * this.shapeScale +
originOffsetX) *
this._sharedData.invScaleX +
this._sharedData.worldInvScale +
offsetX;
Box2D.HEAPF32[(this._verticesBuffer + (offset + 4)) >> 2] =
(this.polygon.vertices[i][1] * this.shapeScale +
originOffsetY) *
this._sharedData.invScaleY +
this._sharedData.worldInvScale +
offsetY;
offset += 8;
}
@@ -755,10 +778,10 @@ namespace gdjs {
? this.shapeDimensionA * this.shapeScale
: this.owner.getWidth() > 0
? this.owner.getWidth()
: 1) * this._sharedData.invScaleX;
: 1) * this._sharedData.worldInvScale;
let height =
this.owner.getHeight() > 0
? this.owner.getHeight() * this._sharedData.invScaleY
? this.owner.getHeight() * this._sharedData.worldInvScale
: 0;
// Angle from custom dimension, otherwise is 0
@@ -787,13 +810,13 @@ namespace gdjs {
? this.shapeDimensionA * this.shapeScale
: this.owner.getWidth() > 0
? this.owner.getWidth()
: 1) * this._sharedData.invScaleX;
: 1) * this._sharedData.worldInvScale;
let height =
(this.shapeDimensionB > 0
? this.shapeDimensionB * this.shapeScale
: this.owner.getHeight() > 0
? this.owner.getHeight()
: 1) * this._sharedData.invScaleY;
: 1) * this._sharedData.worldInvScale;
// Set the shape box, the offset must be added here too
shape.SetAsBox(
@@ -878,9 +901,9 @@ namespace gdjs {
bodyDef.set_position(
this.b2Vec2(
(this.owner.getDrawableX() + this.owner.getWidth() / 2) *
this._sharedData.invScaleX,
this._sharedData.worldInvScale,
(this.owner.getDrawableY() + this.owner.getHeight() / 2) *
this._sharedData.invScaleY
this._sharedData.worldInvScale
)
);
bodyDef.set_angle(gdjs.toRad(this.owner.getAngle()));
@@ -934,13 +957,13 @@ namespace gdjs {
// 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._body.GetPosition().get_x() * this._sharedData.worldScale -
this.owner.getWidth() / 2 +
this.owner.getX() -
this.owner.getDrawableX()
);
this.owner.setY(
this._body.GetPosition().get_y() * this._sharedData.scaleY -
this._body.GetPosition().get_y() * this._sharedData.worldScale -
this.owner.getHeight() / 2 +
this.owner.getY() -
this.owner.getDrawableY()
@@ -993,15 +1016,19 @@ namespace gdjs {
) {
const pos = this.b2Vec2(
(this.owner.getDrawableX() + this.owner.getWidth() / 2) *
this._sharedData.invScaleX,
this._sharedData.worldInvScale,
(this.owner.getDrawableY() + this.owner.getHeight() / 2) *
this._sharedData.invScaleY
this._sharedData.worldInvScale
);
body.SetTransform(pos, gdjs.toRad(this.owner.getAngle()));
body.SetAwake(true);
}
}
getWorldScale(): float {
return this._sharedData.worldScale;
}
getGravityX(): float {
return this._sharedData.gravityX;
}
@@ -1435,7 +1462,7 @@ namespace gdjs {
const body = this._body!;
// Get the linear velocity on X
return body.GetLinearVelocity().get_x() * this._sharedData.scaleX;
return body.GetLinearVelocity().get_x() * this._sharedData.worldScale;
}
setLinearVelocityX(linearVelocityX: float): void {
@@ -1448,7 +1475,7 @@ namespace gdjs {
// Set the linear velocity on X
body.SetLinearVelocity(
this.b2Vec2(
linearVelocityX * this._sharedData.invScaleX,
linearVelocityX * this._sharedData.worldInvScale,
body.GetLinearVelocity().get_y()
)
);
@@ -1462,7 +1489,7 @@ namespace gdjs {
const body = this._body!;
// Get the linear velocity on Y
return body.GetLinearVelocity().get_y() * this._sharedData.scaleY;
return body.GetLinearVelocity().get_y() * this._sharedData.worldScale;
}
setLinearVelocityY(linearVelocityY: float): void {
@@ -1476,7 +1503,7 @@ namespace gdjs {
body.SetLinearVelocity(
this.b2Vec2(
body.GetLinearVelocity().get_x(),
linearVelocityY * this._sharedData.invScaleY
linearVelocityY * this._sharedData.worldInvScale
)
);
}
@@ -1490,8 +1517,8 @@ namespace gdjs {
// Get the linear velocity length
return this.b2Vec2(
body.GetLinearVelocity().get_x() * this._sharedData.scaleX,
body.GetLinearVelocity().get_y() * this._sharedData.scaleY
body.GetLinearVelocity().get_x() * this._sharedData.worldScale,
body.GetLinearVelocity().get_y() * this._sharedData.worldScale
).Length();
}
@@ -1505,8 +1532,8 @@ namespace gdjs {
// Get the linear velocity angle
return gdjs.toDegrees(
Math.atan2(
body.GetLinearVelocity().get_y() * this._sharedData.scaleY,
body.GetLinearVelocity().get_x() * this._sharedData.scaleX
body.GetLinearVelocity().get_y() * this._sharedData.worldScale,
body.GetLinearVelocity().get_x() * this._sharedData.worldScale
)
);
}
@@ -1522,8 +1549,8 @@ namespace gdjs {
angle = gdjs.toRad(angle);
body.SetLinearVelocity(
this.b2Vec2(
linearVelocity * Math.cos(angle) * this._sharedData.invScaleX,
linearVelocity * Math.sin(angle) * this._sharedData.invScaleY
linearVelocity * Math.cos(angle) * this._sharedData.worldInvScale,
linearVelocity * Math.sin(angle) * this._sharedData.worldInvScale
)
);
}
@@ -1580,8 +1607,8 @@ namespace gdjs {
body.ApplyForce(
this.b2Vec2(forceX, forceY),
this.b2Vec2Sec(
positionX * this._sharedData.invScaleX,
positionY * this._sharedData.invScaleY
positionX * this._sharedData.worldInvScale,
positionY * this._sharedData.worldInvScale
),
// TODO Should let Box2d awake the object itself.
false
@@ -1608,8 +1635,8 @@ namespace gdjs {
body.ApplyForce(
this.b2Vec2(length * Math.cos(angle), length * Math.sin(angle)),
this.b2Vec2Sec(
positionX * this._sharedData.invScaleX,
positionY * this._sharedData.invScaleY
positionX * this._sharedData.worldInvScale,
positionY * this._sharedData.worldInvScale
),
// TODO Should let Box2d awake the object itself.
false
@@ -1632,16 +1659,17 @@ namespace gdjs {
// Wake up the object
body.SetAwake(true);
// TODO Optimize this using a unit vector instead of trigonometry.
// Apply the force
const angle = Math.atan2(
towardY * this._sharedData.invScaleY - body.GetPosition().get_y(),
towardX * this._sharedData.invScaleX - body.GetPosition().get_x()
towardY * this._sharedData.worldInvScale - body.GetPosition().get_y(),
towardX * this._sharedData.worldInvScale - body.GetPosition().get_x()
);
body.ApplyForce(
this.b2Vec2(length * Math.cos(angle), length * Math.sin(angle)),
this.b2Vec2Sec(
positionX * this._sharedData.invScaleX,
positionY * this._sharedData.invScaleY
positionX * this._sharedData.worldInvScale,
positionY * this._sharedData.worldInvScale
),
// TODO Should let Box2d awake the object itself.
false
@@ -1667,8 +1695,8 @@ namespace gdjs {
body.ApplyLinearImpulse(
this.b2Vec2(impulseX, impulseY),
this.b2Vec2Sec(
positionX * this._sharedData.invScaleX,
positionY * this._sharedData.invScaleY
positionX * this._sharedData.worldInvScale,
positionY * this._sharedData.worldInvScale
),
// TODO Should let Box2d awake the object itself.
false
@@ -1695,8 +1723,8 @@ namespace gdjs {
body.ApplyLinearImpulse(
this.b2Vec2(length * Math.cos(angle), length * Math.sin(angle)),
this.b2Vec2Sec(
positionX * this._sharedData.invScaleX,
positionY * this._sharedData.invScaleY
positionX * this._sharedData.worldInvScale,
positionY * this._sharedData.worldInvScale
),
// TODO Should let Box2d awake the object itself.
false
@@ -1719,16 +1747,17 @@ namespace gdjs {
// Wake up the object
body.SetAwake(true);
// TODO Optimize this using a unit vector instead of trigonometry.
// Apply the impulse
const angle = Math.atan2(
towardY * this._sharedData.invScaleY - body.GetPosition().get_y(),
towardX * this._sharedData.invScaleX - body.GetPosition().get_x()
towardY * this._sharedData.worldInvScale - body.GetPosition().get_y(),
towardX * this._sharedData.worldInvScale - body.GetPosition().get_x()
);
body.ApplyLinearImpulse(
this.b2Vec2(length * Math.cos(angle), length * Math.sin(angle)),
this.b2Vec2Sec(
positionX * this._sharedData.invScaleX,
positionY * this._sharedData.invScaleY
positionX * this._sharedData.worldInvScale,
positionY * this._sharedData.worldInvScale
),
// TODO Should let Box2d awake the object itself.
false
@@ -1805,7 +1834,7 @@ namespace gdjs {
const body = this._body!;
// Get the mass center on X
return body.GetWorldCenter().get_x() * this._sharedData.scaleX;
return body.GetWorldCenter().get_x() * this._sharedData.worldScale;
}
getMassCenterY(): float {
@@ -1816,7 +1845,7 @@ namespace gdjs {
const body = this._body!;
// Get the mass center on Y
return body.GetWorldCenter().get_y() * this._sharedData.scaleY;
return body.GetWorldCenter().get_y() * this._sharedData.worldScale;
}
// Joints
@@ -1983,8 +2012,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -1992,17 +2021,17 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
jointDef.set_length(
length > 0
? length * this._sharedData.invScaleX
? length * this._sharedData.worldInvScale
: this.b2Vec2(
(x2 - x1) * this._sharedData.invScaleX,
(y2 - y1) * this._sharedData.invScaleY
(x2 - x1) * this._sharedData.worldInvScale,
(y2 - y1) * this._sharedData.worldInvScale
).Length()
);
jointDef.set_frequencyHz(frequency >= 0 ? frequency : 0);
@@ -2031,7 +2060,7 @@ namespace gdjs {
}
// Get the joint length
return joint.GetLength() * this._sharedData.scaleX;
return joint.GetLength() * this._sharedData.worldScale;
}
setDistanceJointLength(jointId: integer | string, length: float): void {
@@ -2049,7 +2078,7 @@ namespace gdjs {
}
// Set the joint length
joint.SetLength(length * this._sharedData.invScaleX);
joint.SetLength(length * this._sharedData.worldInvScale);
// Awake the bodies
joint.GetBodyA().SetAwake(true);
@@ -2148,8 +2177,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
this._sharedData.staticBody.GetLocalPoint(
this.b2Vec2(
x * this._sharedData.invScaleX,
y * this._sharedData.invScaleY
x * this._sharedData.worldInvScale,
y * this._sharedData.worldInvScale
)
)
);
@@ -2157,8 +2186,8 @@ namespace gdjs {
jointDef.set_localAnchorB(
body.GetLocalPoint(
this.b2Vec2(
x * this._sharedData.invScaleX,
y * this._sharedData.invScaleY
x * this._sharedData.worldInvScale,
y * this._sharedData.worldInvScale
)
)
);
@@ -2233,8 +2262,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -2242,8 +2271,8 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
@@ -2534,8 +2563,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -2543,8 +2572,8 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
@@ -2564,13 +2593,17 @@ namespace gdjs {
// The translation range must include zero
jointDef.set_lowerTranslation(
lowerTranslation < 0 ? lowerTranslation * this._sharedData.invScaleX : 0
lowerTranslation < 0
? lowerTranslation * this._sharedData.worldInvScale
: 0
);
jointDef.set_upperTranslation(
upperTranslation > 0 ? upperTranslation * this._sharedData.invScaleX : 0
upperTranslation > 0
? upperTranslation * this._sharedData.worldInvScale
: 0
);
jointDef.set_enableMotor(enableMotor);
jointDef.set_motorSpeed(motorSpeed * this._sharedData.invScaleX);
jointDef.set_motorSpeed(motorSpeed * this._sharedData.worldInvScale);
jointDef.set_maxMotorForce(maxMotorForce);
jointDef.set_collideConnected(collideConnected);
@@ -2633,7 +2666,7 @@ namespace gdjs {
}
// Get the joint current translation
return joint.GetJointTranslation() * this._sharedData.scaleX;
return joint.GetJointTranslation() * this._sharedData.worldScale;
}
getPrismaticJointSpeed(jointId: integer | string): float {
@@ -2648,7 +2681,7 @@ namespace gdjs {
}
// Get the joint speed
return joint.GetJointSpeed() * this._sharedData.scaleX;
return joint.GetJointSpeed() * this._sharedData.worldScale;
}
isPrismaticJointLimitsEnabled(jointId: integer | string): boolean {
@@ -2696,7 +2729,7 @@ namespace gdjs {
}
// Get the joint lower limit
return joint.GetLowerLimit() * this._sharedData.scaleX;
return joint.GetLowerLimit() * this._sharedData.worldScale;
}
getPrismaticJointMaxTranslation(jointId: integer | string): float {
@@ -2711,7 +2744,7 @@ namespace gdjs {
}
// Get the joint upper angle
return joint.GetUpperLimit() * this._sharedData.scaleX;
return joint.GetUpperLimit() * this._sharedData.worldScale;
}
setPrismaticJointLimits(
@@ -2742,8 +2775,8 @@ namespace gdjs {
// Set the joint limits
joint.SetLimits(
lowerTranslation * this._sharedData.invScaleX,
upperTranslation * this._sharedData.invScaleX
lowerTranslation * this._sharedData.worldInvScale,
upperTranslation * this._sharedData.worldInvScale
);
}
@@ -2792,7 +2825,7 @@ namespace gdjs {
}
// Get the joint motor speed
return joint.GetMotorSpeed() * this._sharedData.scaleX;
return joint.GetMotorSpeed() * this._sharedData.worldScale;
}
setPrismaticJointMotorSpeed(jointId: integer | string, speed): void {
@@ -2807,7 +2840,7 @@ namespace gdjs {
}
// Set the joint motor speed
joint.SetMotorSpeed(speed * this._sharedData.invScaleX);
joint.SetMotorSpeed(speed * this._sharedData.worldInvScale);
}
getPrismaticJointMaxMotorForce(jointId: integer | string): float {
@@ -2904,8 +2937,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -2913,37 +2946,37 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
jointDef.set_groundAnchorA(
this.b2Vec2(
groundX1 * this._sharedData.invScaleX,
groundY1 * this._sharedData.invScaleY
groundX1 * this._sharedData.worldInvScale,
groundY1 * this._sharedData.worldInvScale
)
);
jointDef.set_groundAnchorB(
this.b2Vec2(
groundX2 * this._sharedData.invScaleX,
groundY2 * this._sharedData.invScaleY
groundX2 * this._sharedData.worldInvScale,
groundY2 * this._sharedData.worldInvScale
)
);
jointDef.set_lengthA(
lengthA > 0
? lengthA * this._sharedData.invScaleX
? lengthA * this._sharedData.worldInvScale
: this.b2Vec2(
(groundX1 - x1) * this._sharedData.invScaleX,
(groundY1 - y1) * this._sharedData.invScaleY
(groundX1 - x1) * this._sharedData.worldInvScale,
(groundY1 - y1) * this._sharedData.worldInvScale
).Length()
);
jointDef.set_lengthB(
lengthB > 0
? lengthB * this._sharedData.invScaleX
? lengthB * this._sharedData.worldInvScale
: this.b2Vec2(
(groundX2 - x2) * this._sharedData.invScaleX,
(groundY2 - y2) * this._sharedData.invScaleY
(groundX2 - x2) * this._sharedData.worldInvScale,
(groundY2 - y2) * this._sharedData.worldInvScale
).Length()
);
jointDef.set_ratio(ratio > 0 ? ratio : 1);
@@ -2971,7 +3004,7 @@ namespace gdjs {
}
// Get the joint ground anchor
return joint.GetGroundAnchorA().get_x() * this._sharedData.scaleX;
return joint.GetGroundAnchorA().get_x() * this._sharedData.worldScale;
}
getPulleyJointFirstGroundAnchorY(jointId: integer | string): float {
@@ -2984,7 +3017,7 @@ namespace gdjs {
}
// Get the joint ground anchor
return joint.GetGroundAnchorA().get_y() * this._sharedData.scaleY;
return joint.GetGroundAnchorA().get_y() * this._sharedData.worldScale;
}
getPulleyJointSecondGroundAnchorX(jointId: integer | string): float {
@@ -2997,7 +3030,7 @@ namespace gdjs {
}
// Get the joint ground anchor
return joint.GetGroundAnchorB().get_x() * this._sharedData.scaleX;
return joint.GetGroundAnchorB().get_x() * this._sharedData.worldScale;
}
getPulleyJointSecondGroundAnchorY(jointId: integer | string): float {
@@ -3010,7 +3043,7 @@ namespace gdjs {
}
// Get the joint ground anchor
return joint.GetGroundAnchorB().get_y() * this._sharedData.scaleY;
return joint.GetGroundAnchorB().get_y() * this._sharedData.worldScale;
}
getPulleyJointFirstLength(jointId: integer | string): float {
@@ -3023,7 +3056,7 @@ namespace gdjs {
}
// Get the joint length
return joint.GetCurrentLengthA() * this._sharedData.scaleX;
return joint.GetCurrentLengthA() * this._sharedData.worldScale;
}
getPulleyJointSecondLength(jointId: integer | string): float {
@@ -3036,7 +3069,7 @@ namespace gdjs {
}
// Get the joint length
return joint.GetCurrentLengthB() * this._sharedData.scaleX;
return joint.GetCurrentLengthB() * this._sharedData.worldScale;
}
getPulleyJointRatio(jointId: integer | string): float {
@@ -3188,8 +3221,8 @@ namespace gdjs {
jointDef.set_bodyB(body);
jointDef.set_target(
this.b2Vec2(
targetX * this._sharedData.invScaleX,
targetY * this._sharedData.invScaleY
targetX * this._sharedData.worldInvScale,
targetY * this._sharedData.worldInvScale
)
);
jointDef.set_maxForce(maxForce >= 0 ? maxForce : 0);
@@ -3217,7 +3250,7 @@ namespace gdjs {
}
// Get the joint target X
return joint.GetTarget().get_x() * this._sharedData.scaleX;
return joint.GetTarget().get_x() * this._sharedData.worldScale;
}
getMouseJointTargetY(jointId: integer | string): float {
@@ -3229,7 +3262,7 @@ namespace gdjs {
}
// Get the joint target Y
return joint.GetTarget().get_y() * this._sharedData.scaleY;
return joint.GetTarget().get_y() * this._sharedData.worldScale;
}
setMouseJointTarget(
@@ -3247,8 +3280,8 @@ namespace gdjs {
// Set the joint target
joint.SetTarget(
this.b2Vec2(
targetX * this._sharedData.invScaleX,
targetY * this._sharedData.invScaleY
targetX * this._sharedData.worldInvScale,
targetY * this._sharedData.worldInvScale
)
);
@@ -3386,8 +3419,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -3395,8 +3428,8 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
@@ -3451,7 +3484,7 @@ namespace gdjs {
}
// Get the joint current translation
return joint.GetJointTranslation() * this._sharedData.scaleX;
return joint.GetJointTranslation() * this._sharedData.worldScale;
}
getWheelJointSpeed(jointId: integer | string): float {
@@ -3668,8 +3701,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -3677,8 +3710,8 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
@@ -3817,8 +3850,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -3826,17 +3859,17 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
jointDef.set_maxLength(
maxLength > 0
? maxLength * this._sharedData.invScaleX
? maxLength * this._sharedData.worldInvScale
: this.b2Vec2(
(x2 - x1) * this._sharedData.invScaleX,
(y2 - y1) * this._sharedData.invScaleY
(x2 - x1) * this._sharedData.worldInvScale,
(y2 - y1) * this._sharedData.worldInvScale
).Length()
);
jointDef.set_collideConnected(collideConnected);
@@ -3863,7 +3896,7 @@ namespace gdjs {
}
// Get the joint maximum length
return joint.GetMaxLength() * this._sharedData.scaleX;
return joint.GetMaxLength() * this._sharedData.worldScale;
}
setRopeJointMaxLength(jointId: integer | string, maxLength: float): void {
@@ -3881,7 +3914,7 @@ namespace gdjs {
}
// Set the joint maximum length
joint.SetMaxLength(maxLength * this._sharedData.invScaleX);
joint.SetMaxLength(maxLength * this._sharedData.worldInvScale);
// Awake the bodies
joint.GetBodyA().SetAwake(true);
@@ -3927,8 +3960,8 @@ namespace gdjs {
jointDef.set_localAnchorA(
body.GetLocalPoint(
this.b2Vec2(
x1 * this._sharedData.invScaleX,
y1 * this._sharedData.invScaleY
x1 * this._sharedData.worldInvScale,
y1 * this._sharedData.worldInvScale
)
)
);
@@ -3936,8 +3969,8 @@ namespace gdjs {
jointDef.set_localAnchorB(
otherBody.GetLocalPoint(
this.b2Vec2(
x2 * this._sharedData.invScaleX,
y2 * this._sharedData.invScaleY
x2 * this._sharedData.worldInvScale,
y2 * this._sharedData.worldInvScale
)
)
);
@@ -4061,8 +4094,8 @@ namespace gdjs {
jointDef.set_bodyB(otherBody);
jointDef.set_linearOffset(
this.b2Vec2(
offsetX * this._sharedData.invScaleX,
offsetY * this._sharedData.invScaleY
offsetX * this._sharedData.worldInvScale,
offsetY * this._sharedData.worldInvScale
)
);
jointDef.set_angularOffset(gdjs.toRad(offsetAngle));
@@ -4095,7 +4128,7 @@ namespace gdjs {
}
// Get the joint offset
return joint.GetLinearOffset().get_x() * this._sharedData.scaleX;
return joint.GetLinearOffset().get_x() * this._sharedData.worldScale;
}
getMotorJointOffsetY(jointId: integer | string): float {
@@ -4108,7 +4141,7 @@ namespace gdjs {
}
// Get the joint offset
return joint.GetLinearOffset().get_y() * this._sharedData.scaleY;
return joint.GetLinearOffset().get_y() * this._sharedData.worldScale;
}
setMotorJointOffset(
@@ -4127,8 +4160,8 @@ namespace gdjs {
// Set the joint offset
joint.SetLinearOffset(
this.b2Vec2(
offsetX * this._sharedData.invScaleX,
offsetY * this._sharedData.invScaleY
offsetX * this._sharedData.worldInvScale,
offsetY * this._sharedData.worldInvScale
)
);
}

View File

@@ -843,7 +843,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"CppPlatform/Extensions/platformicon.png",
"PlatformBehavior",
std::make_shared<PlatformBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
std::make_shared<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
aut.AddAction("ChangePlatformType",
_("Platform type"),

View File

@@ -61,6 +61,7 @@ PlatformerObjectBehavior::GetProperties(
gd::String::From(behaviorContent.GetDoubleAttribute("jumpSpeed")));
properties["JumpSustainTime"]
.SetLabel(_("Jump sustain time"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Jump"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond())
@@ -79,6 +80,7 @@ PlatformerObjectBehavior::GetProperties(
behaviorContent.GetDoubleAttribute("maxFallingSpeed")));
properties["LadderClimbingSpeed"]
.SetLabel(_("Ladder climbing speed"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ladder"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixelSpeed())
@@ -107,12 +109,14 @@ PlatformerObjectBehavior::GetProperties(
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
properties["IgnoreDefaultControls"]
.SetLabel(_("Default controls"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
? "false"
: "true")
.SetType("Boolean");
properties["SlopeMaxAngle"]
.SetLabel(_("Slope max. angle"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Walk"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
@@ -120,6 +124,7 @@ PlatformerObjectBehavior::GetProperties(
behaviorContent.GetDoubleAttribute("slopeMaxAngle")));
properties["CanGrabPlatforms"]
.SetLabel(_("Can grab platform ledges"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ledge"))
.SetValue(behaviorContent.GetBoolAttribute("canGrabPlatforms", false)
? "true"
@@ -128,6 +133,7 @@ PlatformerObjectBehavior::GetProperties(
properties["CanGrabWithoutMoving"]
.SetLabel(_("Automatically grab platform ledges without having to move "
"horizontally"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ledge"))
.SetValue(behaviorContent.GetBoolAttribute("canGrabWithoutMoving", false)
? "true"
@@ -135,6 +141,7 @@ PlatformerObjectBehavior::GetProperties(
.SetType("Boolean");
properties["YGrabOffset"]
.SetLabel(_("Grab offset on Y axis"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ledge"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
@@ -142,6 +149,7 @@ PlatformerObjectBehavior::GetProperties(
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
properties["XGrabTolerance"]
.SetLabel(_("Grab tolerance on X axis"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ledge"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
@@ -158,6 +166,7 @@ PlatformerObjectBehavior::GetProperties(
.SetType("Boolean");
properties["CanGoDownFromJumpthru"]
.SetLabel(_("Can go down from jumpthru platforms"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Walk"))
.SetValue(behaviorContent.GetBoolAttribute("canGoDownFromJumpthru", false)
? "true"

View File

@@ -166,6 +166,19 @@ namespace gdjs {
return true;
}
/**
* Initialize the extra parameters that could be set for an instance.
* @param initialInstanceData The extra parameters
*/
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
}
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
//We redefine stepBehaviorsPreEvents just to clear the graphics before running events.
if (this._clearBetweenFrames) {

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