Compare commits

...

81 Commits

Author SHA1 Message Date
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
D8H
75566c9f38 Avoid to use an intermediate ThreeGroup for custom objects in the editor (#6837)
- 2D custom objects displayed in the editor now also use the origin point of their 1st child unless an hidden property force a location.
2024-08-07 18:24:39 +02:00
AlexandreS
8db8cbe3c2 Display the number of tilemap objects available in premium asset packs before purchase (#6851) 2024-08-07 16:40:50 +02:00
D8H
dd8c5dce2e Fix access to the prefab editor by hiding the drop-down menu item (#6850) 2024-08-06 11:47:49 +02:00
Arthur Pacaud (arthuro555)
2810056626 Allow hot-reloading of 3D models (#6848) 2024-08-05 21:33:14 +02:00
D8H
d082d4f5a4 Fix child object recycling in custom objects (#6846) 2024-08-05 10:08:58 +02:00
Florian Rival
eaa0f4077f Bump newIDE version (#6844) 2024-08-02 12:01:32 +02:00
D8H
7d73f58bc4 Fix a crash when renaming a child variable (#6839) 2024-08-01 16:13:51 +02:00
AlexandreS
9171b40429 Fix sentence in tile id action/condition (#6836) 2024-07-31 17:44:28 +02:00
496 changed files with 19708 additions and 9344 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

@@ -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();
}

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);
}
}
}

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;
@@ -90,10 +105,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

@@ -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,7 @@ EventsBasedObject::EventsBasedObject()
isRenderedIn3D(false),
isAnimatable(false),
isTextContainer(false),
isInnerAreaFollowingParentSize(false),
areaMinX(0),
areaMinY(0),
areaMinZ(0),
@@ -37,6 +38,9 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
if (isTextContainer) {
element.SetBoolAttribute("isTextContainer", true);
}
if (isInnerAreaFollowingParentSize) {
element.SetBoolAttribute("isInnerAreaFollowingParentSize", true);
}
element.SetIntAttribute("areaMinX", areaMinX);
element.SetIntAttribute("areaMinY", areaMinY);
element.SetIntAttribute("areaMinZ", areaMinZ);
@@ -59,6 +63,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);

View File

@@ -101,11 +101,35 @@ 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 &
MarkAsInnerAreaExpandingWithParent(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 Return true if the object needs a TextContainer capability.
*/
@@ -279,6 +303,7 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
bool isRenderedIn3D;
bool isAnimatable;
bool isTextContainer;
bool isInnerAreaFollowingParentSize;
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

@@ -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

@@ -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,16 @@ class GD_CORE_API PropertyDescriptor {
PropertyDescriptor(gd::String propertyValue)
: currentValue(propertyValue), type("string"), label(""), hidden(false),
deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
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()){};
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
/**
* \brief Destructor
@@ -109,7 +112,7 @@ class GD_CORE_API PropertyDescriptor {
extraInformation.push_back(info);
return *this;
}
/**
* \brief Change the unit of measurement of the property value.
*/
@@ -128,7 +131,7 @@ class GD_CORE_API PropertyDescriptor {
const std::vector<gd::String>& GetExtraInfo() const {
return extraInformation;
}
std::vector<gd::String>& GetExtraInfo() {
return extraInformation;
}
@@ -172,6 +175,13 @@ class GD_CORE_API PropertyDescriptor {
*/
bool IsAdvanced() const { return advanced; }
QuickCustomization::Visibility GetQuickCustomizationVisibility() const { return quickCustomizationVisibility; }
PropertyDescriptor& SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
return *this;
}
/** \name Serialization
*/
///@{
@@ -212,6 +222,7 @@ class GD_CORE_API PropertyDescriptor {
bool deprecated;
bool advanced;
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

@@ -454,7 +454,7 @@ bool Variable::operator==(const gd::Variable &variable) const {
const auto &child = pair.second;
auto it = variable.children.find(name);
if (it == children.end()) {
if (it == variable.children.end()) {
return false;
}
auto &otherChild = it->second;

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

@@ -304,8 +304,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 +332,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

@@ -118,6 +118,19 @@ namespace gdjs {
this._materialType = this._convertMaterialType(
objectData.content.materialType
);
this.onModelChanged(objectData);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
}
/**
* To be called after the renderer loaded a Model resource:
* - After the renderer was instantiated
* - After reloading the model
*/
private onModelChanged(objectData) {
this._updateModel(objectData);
if (this._animations.length > 0) {
this._renderer.playAnimation(
@@ -125,9 +138,6 @@ namespace gdjs {
this._animations[0].loop
);
}
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
}
updateFromObjectData(
@@ -135,18 +145,7 @@ namespace gdjs {
newObjectData: Model3DObjectData
): boolean {
super.updateFromObjectData(oldObjectData, newObjectData);
if (
oldObjectData.content.width !== newObjectData.content.width ||
oldObjectData.content.height !== newObjectData.content.height ||
oldObjectData.content.depth !== newObjectData.content.depth ||
oldObjectData.content.rotationX !== newObjectData.content.rotationX ||
oldObjectData.content.rotationY !== newObjectData.content.rotationY ||
oldObjectData.content.rotationZ !== newObjectData.content.rotationZ ||
oldObjectData.content.keepAspectRatio !==
newObjectData.content.keepAspectRatio
) {
this._updateModel(newObjectData);
}
if (
oldObjectData.content.materialType !==
newObjectData.content.materialType
@@ -154,6 +153,24 @@ namespace gdjs {
this._materialType = this._convertMaterialType(
newObjectData.content.materialType
);
}
if (
oldObjectData.content.modelResourceName !==
newObjectData.content.modelResourceName
) {
this._reloadModel(newObjectData);
} else if (
oldObjectData.content.width !== newObjectData.content.width ||
oldObjectData.content.height !== newObjectData.content.height ||
oldObjectData.content.depth !== newObjectData.content.depth ||
oldObjectData.content.rotationX !== newObjectData.content.rotationX ||
oldObjectData.content.rotationY !== newObjectData.content.rotationY ||
oldObjectData.content.rotationZ !== newObjectData.content.rotationZ ||
oldObjectData.content.keepAspectRatio !==
newObjectData.content.keepAspectRatio ||
oldObjectData.content.materialType !==
newObjectData.content.materialType
) {
this._updateModel(newObjectData);
}
if (
@@ -218,6 +235,12 @@ namespace gdjs {
}
}
_reloadModel(objectData: Model3DObjectData) {
this._modelResourceName = objectData.content.modelResourceName;
this._renderer._reloadModel(this, this._runtimeScene);
this.onModelChanged(objectData);
}
_updateModel(objectData: Model3DObjectData) {
const rotationX = objectData.content.rotationX || 0;
const rotationY = objectData.content.rotationY || 0;

View File

@@ -236,6 +236,20 @@ namespace gdjs {
}
}
/**
* `_updateModel` should always be called after this method.
* Ideally, use `Model3DRuntimeObject#_reloadModel` instead.
*/
_reloadModel(
runtimeObject: Model3DRuntimeObject,
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._originalModel = instanceContainer
.getGame()
.getModel3DManager()
.getModel(runtimeObject._modelResourceName);
}
_updateModel(
rotationX: float,
rotationY: float,

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");
}
@@ -69,6 +72,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window right"))
.AddExtraInfo(_("Proportional"))
.SetDescription(_("Anchor the left edge of the object on X axis."));
@@ -79,6 +83,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window right"))
.AddExtraInfo(_("Proportional"))
.SetDescription(_("Anchor the right edge of the object on X axis."));
@@ -89,6 +94,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window bottom"))
.AddExtraInfo(_("Proportional"))
.SetDescription(_("Anchor the top edge of the object on Y axis."));
@@ -99,6 +105,7 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
.AddExtraInfo(_("Window center"))
.AddExtraInfo(_("Window bottom"))
.AddExtraInfo(_("Proportional"))
.SetDescription(_("Anchor the bottom edge of the object on Y axis."));
@@ -127,6 +134,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 +148,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;
}
@@ -172,4 +183,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

@@ -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

@@ -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

@@ -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

@@ -73,6 +73,38 @@ module.exports = {
.setFunctionName('setAnimationMixingDuration')
.setGetter('getAnimationMixingDuration');
object
.addExpressionAndCondition(
'number',
'PointAttachmentX',
_('Point attachment X position'),
_('x position of spine point attachment'),
_('x position of spine _PARAM1_ point attachment for _PARAM2_ slot'),
_('Animations and images'),
'JsPlatform/Extensions/spine.svg'
)
.addParameter('object', _('Spine'), 'SpineObject')
.addParameter('string', _('Attachment name'))
.addParameter('string', _('Slot name (use "" if names are the same)'))
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('getPointAttachmentX');
object
.addExpressionAndCondition(
'number',
'PointAttachmentY',
_('Point attachment Y position'),
_('y position of spine point attachment'),
_('y position of spine _PARAM1_ point attachment for _PARAM2_ slot'),
_('Animations and images'),
'JsPlatform/Extensions/spine.svg'
)
.addParameter('object', _('Spine'), 'SpineObject')
.addParameter('string', _('Attachment name'))
.addParameter('string', _('Slot name (use "" if names are the same)'))
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('getPointAttachmentY');
return extension;
},

View File

@@ -2,6 +2,21 @@ namespace gdjs {
const isSpine = (obj: any): obj is pixi_spine.Spine =>
obj instanceof pixi_spine.Spine;
// See https://github.com/pixijs/spine/issues/562
// IPointAttachment is not declared and exported but its implementation does exist and it is used in runtime
interface IPointAttachment extends pixi_spine.IVertexAttachment {
computeWorldPosition(
bone: pixi_spine.IBone,
point: pixi_spine.Vector2
): pixi_spine.Vector2;
computeWorldRotation(bone: pixi_spine.IBone): number;
}
const isPointAttachment = (
attachment: pixi_spine.IAttachment
): attachment is IPointAttachment =>
!!attachment && attachment.type === pixi_spine.AttachmentType.Point;
export class SpineRuntimeObjectPixiRenderer {
private _object: gdjs.SpineRuntimeObject;
private _rendererObject: pixi_spine.Spine | PIXI.Container;
@@ -182,6 +197,44 @@ namespace gdjs {
return this._isAnimationComplete;
}
getPointAttachmentPosition(
attachmentName: string,
slotName?: string
): pixi_spine.Vector2 {
if (!slotName) {
slotName = attachmentName;
}
if (!isSpine(this._rendererObject)) {
return new pixi_spine.Vector2(
this._rendererObject.x,
this._rendererObject.y
);
}
const slot = this._rendererObject.skeleton.findSlot(slotName);
if (!slot) {
throw new Error(
`Unable to find ${slotName} slot name for ${attachmentName} point attachment.`
);
}
const attachment = this._rendererObject.skeleton.getAttachmentByName(
slotName,
attachmentName
);
if (!isPointAttachment(attachment)) {
throw new Error(
`Unable to find ${attachmentName} point attachment with ${slotName} slot name.`
);
}
return new PIXI.Matrix()
.rotate(this._rendererObject.rotation)
.scale(this._rendererObject.scale.x, this._rendererObject.scale.y)
.translate(this._rendererObject.x, this._rendererObject.y)
.apply(
attachment.computeWorldPosition(slot.bone, new pixi_spine.Vector2())
);
}
private constructRendererObject(): pixi_spine.Spine | PIXI.Container {
const game = this.instanceContainer.getGame();
const spineManager = game.getSpineManager();

View File

@@ -67,7 +67,7 @@ namespace gdjs {
this._animations = objectData.content.animations;
this._originalScale = objectData.content.scale;
this.spineResourceName = objectData.content.spineResourceName;
this._animationMixingDuration = 0.1;
this._animationMixingDuration = 0;
this._renderer = new gdjs.SpineRuntimeObjectRenderer(
this,
instanceContainer
@@ -500,6 +500,16 @@ namespace gdjs {
this._isPausedFrameDirty = true;
}
getPointAttachmentX(attachmentName: string, slotName?: string): number {
return this._renderer.getPointAttachmentPosition(attachmentName, slotName)
.x;
}
getPointAttachmentY(attachmentName: string, slotName?: string): number {
return this._renderer.getPointAttachmentPosition(attachmentName, slotName)
.y;
}
getAnimationDuration(): number {
if (this._animations.length === 0) {
return 0;

View File

@@ -278,7 +278,7 @@ module.exports = {
// Properties expressions/conditions/actions:
// Deprecated
// Deprecated, see TextContainerCapability
object
.addExpressionAndConditionAndAction(
'string',
@@ -298,6 +298,7 @@ module.exports = {
.setFunctionName('setText')
.setGetter('getText');
// Deprecated, see TextContainerCapability
object
.addStrExpression(
'Text',
@@ -306,6 +307,7 @@ module.exports = {
'',
'res/conditions/text24_black.png'
)
.setHidden()
.addParameter('object', _('Text input'), 'TextInputObject', false)
.setFunctionName('getText');

View File

@@ -49,7 +49,7 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
"res/actions/font.png")
.AddParameter("object", _("Object"), "Text")
.AddParameter("police", _("Font"))
.AddParameter("fontResource", _("Font resource name"))
.SetFunctionName("ChangeFont");
obj.AddAction(

View File

@@ -38,6 +38,9 @@ class TextObjectJsExtension : public gd::PlatformExtension {
GetAllExpressionsForObject("TextObject::Text")["FontSize"]
.SetFunctionName("getCharacterSize");
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]
.SetFunctionName("setFontName");
GetAllActionsForObject("TextObject::Text")["TextObject::SetBold"]
.SetFunctionName("setBold");
GetAllConditionsForObject("TextObject::Text")["TextObject::IsBold"]
@@ -188,8 +191,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
.SetFunctionName("setShadow");
// Unimplemented actions and conditions:
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]
.SetFunctionName("");
GetAllActionsForObject("TextObject::Text")["TextObject::SetUnderlined"]
.SetFunctionName("");
GetAllConditionsForObject("TextObject::Text")["TextObject::IsUnderlined"]

View File

@@ -184,7 +184,6 @@ const defineTileMap = function (extension, _, gd) {
objectTileMap
)
.setCategoryFullName(_('Advanced'))
.addDefaultBehavior('EffectCapability::EffectBehavior')
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
@@ -721,7 +720,6 @@ const defineSimpleTileMap = function (extension, _, gd) {
objectSimpleTileMap
)
.setCategoryFullName(_('General'))
.addDefaultBehavior('EffectCapability::EffectBehavior')
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
@@ -888,7 +886,7 @@ const defineSimpleTileMap = function (extension, _, gd) {
'TileIdAtGrid',
_('Tile (on the grid)'),
_('the id of the tile at the grid coordinates'),
_('the tile id at grid coordinates _PARAM2_ ; _PARAM3_'),
_('the tile id at grid coordinates _PARAM3_ ; _PARAM4_'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
@@ -1240,7 +1238,6 @@ const defineCollisionMask = function (extension, _, gd) {
collisionMaskObject
)
.setCategoryFullName(_('Advanced'))
.addDefaultBehavior('EffectCapability::EffectBehavior')
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.setIncludeFile('Extensions/TileMap/tilemapcollisionmaskruntimeobject.js')

View File

@@ -96,6 +96,16 @@ namespace gdjs {
(this._transformationUpToDateCount + 1) % Number.MAX_SAFE_INTEGER;
}
invalidateTile(layerIndex: integer, x: integer, y: integer) {
const layer = this.getLayer(layerIndex);
if (layer) {
const tile = layer.get(x, y);
if (tile) {
tile.invalidate();
}
}
}
/**
* @returns The tile map width in pixels.
*/
@@ -690,6 +700,28 @@ namespace gdjs {
this.affineTransformationUpToDateCount = this.layer.tileMap._transformationUpToDateCount;
}
invalidate() {
this.affineTransformationUpToDateCount = -1;
// Also invalidate neighbors because their hit boxes may need to be
// extended differently.
let neighbor = this.layer.get(this.x - 1, this.y);
if (neighbor) {
neighbor.affineTransformationUpToDateCount = -1;
}
neighbor = this.layer.get(this.x + 1, this.y);
if (neighbor) {
neighbor.affineTransformationUpToDateCount = -1;
}
neighbor = this.layer.get(this.x, this.y - 1);
if (neighbor) {
neighbor.affineTransformationUpToDateCount = -1;
}
neighbor = this.layer.get(this.x, this.y + 1);
if (neighbor) {
neighbor.affineTransformationUpToDateCount = -1;
}
}
/**
* @returns The hitboxes of this tile in the scene basis.
*/
@@ -701,7 +733,6 @@ namespace gdjs {
const definition = this.getDefinition();
if (!definition) {
this._setHitboxesUpToDate();
// It should already be []
this.hitBoxes.length = 0;
return this.hitBoxes;
}
@@ -709,14 +740,100 @@ namespace gdjs {
const definitionHitboxes = definition.getHitBoxes(tag);
if (!definitionHitboxes) {
this._setHitboxesUpToDate();
// It should already be []
this.hitBoxes.length = 0;
return this.hitBoxes;
}
const layerTransformation = this.layer.tileMap.getTransformation();
const width = this.layer.tileMap.getTileWidth();
const height = this.layer.tileMap.getTileHeight();
const tileMap = this.layer.tileMap;
const width = tileMap.getTileWidth();
const height = tileMap.getTileHeight();
// Extend the hit boxes.
// It avoids small objects to be pushed side way into a wall when they
// should be pop out of the wall.
const hasFullHitBox =
definitionHitboxes.length === 1 && definition.hasFullHitBox(tag);
if (hasFullHitBox) {
const isLeftFull = this._hasNeighborFullHitBox(-1, 0);
const isRightFull = this._hasNeighborFullHitBox(1, 0);
const isTopFull = this._hasNeighborFullHitBox(0, -1);
const isBottomFull = this._hasNeighborFullHitBox(0, 1);
let hitBoxesCount = 0;
if (isLeftFull || isRightFull) {
let minX = isLeftFull ? -width : 0;
let maxX = isRightFull ? 2 * width : width;
if (hitBoxesCount >= this.hitBoxes.length) {
this.hitBoxes[hitBoxesCount] = gdjs.Polygon.createRectangle(0, 0);
}
TransformedCollisionTile.setRectangle(
this.hitBoxes[hitBoxesCount],
minX,
0,
maxX,
height
);
hitBoxesCount++;
}
if (isTopFull || isBottomFull) {
let minY = isTopFull ? -height : 0;
let maxY = isBottomFull ? 2 * height : height;
if (hitBoxesCount >= this.hitBoxes.length) {
this.hitBoxes[hitBoxesCount] = gdjs.Polygon.createRectangle(0, 0);
}
TransformedCollisionTile.setRectangle(
this.hitBoxes[hitBoxesCount],
0,
minY,
width,
maxY
);
hitBoxesCount++;
}
if (hitBoxesCount === 0) {
if (this.hitBoxes.length === 0) {
this.hitBoxes[0] = gdjs.Polygon.createRectangle(0, 0);
}
TransformedCollisionTile.setRectangle(
this.hitBoxes[0],
0,
0,
width,
height
);
hitBoxesCount++;
}
this.hitBoxes.length = hitBoxesCount;
} else {
for (
let polygonIndex = 0;
polygonIndex < definitionHitboxes.length;
polygonIndex++
) {
const defPolygon = definitionHitboxes[polygonIndex];
if (polygonIndex >= this.hitBoxes.length) {
// This can't happen in practice as only the simple tile map can be
// modify and it only contains full hit boxes.
this.hitBoxes[polygonIndex] = gdjs.Polygon.createRectangle(0, 0);
}
const polygon = this.hitBoxes[polygonIndex];
for (
let vertexIndex = 0;
vertexIndex < polygon.vertices.length;
vertexIndex++
) {
const defVertex = defPolygon[vertexIndex];
const vertex = polygon.vertices[vertexIndex];
vertex[0] = defVertex[0];
vertex[1] = defVertex[1];
}
}
}
// Transform the hit boxes.
const tileTransformation =
TransformedCollisionTile.workingTransformation;
@@ -731,16 +848,12 @@ namespace gdjs {
tileTransformation.flipX(width / 2);
tileTransformation.rotateAround(Math.PI / 2, width / 2, height / 2);
}
tileTransformation.preConcatenate(layerTransformation);
// The tile map can't change at runtime so the existing arrays can be
// reused safely.
tileTransformation.preConcatenate(tileMap.getTransformation());
for (
let polygonIndex = 0;
polygonIndex < this.hitBoxes.length;
polygonIndex++
) {
const defPolygon = definitionHitboxes[polygonIndex];
const polygon = this.hitBoxes[polygonIndex];
for (
@@ -748,15 +861,42 @@ namespace gdjs {
vertexIndex < polygon.vertices.length;
vertexIndex++
) {
const defVertex = defPolygon[vertexIndex];
const vertex = polygon.vertices[vertexIndex];
tileTransformation.transform(defVertex, vertex);
tileTransformation.transform(vertex, vertex);
}
}
this._setHitboxesUpToDate();
return this.hitBoxes;
}
private _hasNeighborFullHitBox(deltaX: integer, deltaY: integer) {
const sourceLayer = this.layer._source;
const tileId = sourceLayer.getTileId(this.x + deltaX, this.y + deltaY);
const tileDefinition =
tileId && this.layer.tileMap.getTileDefinition(tileId);
return (
tileDefinition && tileDefinition.hasFullHitBox(this.layer.tileMap.tag)
);
}
private static setRectangle(
polygon: gdjs.Polygon,
minX: float,
minY: float,
maxX: float,
maxY: float
) {
const vertices = polygon.vertices;
vertices[0][0] = minX;
vertices[0][1] = minY;
vertices[1][0] = maxX;
vertices[1][1] = minY;
vertices[2][0] = maxX;
vertices[2][1] = maxY;
vertices[3][0] = minX;
vertices[3][1] = maxY;
}
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"TiledTileMapLoader.d.ts","sourceRoot":"","sources":["../../../src/load/tiled/TiledTileMapLoader.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EAGhB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAO7C;;GAEG;AACH,yBAAiB,kBAAkB,CAAC;IAClC;;;;;;OAMG;IACH,SAAgB,IAAI,CAClB,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,GAAG,GACR,eAAe,GAAG,IAAI,CA2KxB;CACF"}
{"version":3,"file":"TiledTileMapLoader.d.ts","sourceRoot":"","sources":["../../../src/load/tiled/TiledTileMapLoader.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EAGhB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAO7C;;GAEG;AACH,yBAAiB,kBAAkB,CAAC;IAClC;;;;;;OAMG;IACH,SAAgB,IAAI,CAClB,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,GAAG,GACR,eAAe,GAAG,IAAI,CAkLxB;CACF"}

View File

@@ -431,8 +431,13 @@ export declare class TileDefinition {
* Add a polygon for the collision layer
* @param tag The tag to allow collision layer filtering.
* @param polygon The polygon to use for collisions.
* @param hasFullHitBox Set to `true` when the hitBox cover the whole tile.
*/
addHitBox(tag: string, polygon: PolygonVertices): void;
addHitBox(
tag: string,
polygon: PolygonVertices,
hasFullHitBox: boolean
): void;
/**
* This property is used by {@link TransformedCollisionTileMap}
* to make collision classes.
@@ -446,6 +451,12 @@ export declare class TileDefinition {
* @returns The hit boxes for this tile.
*/
getHitBoxes(tag: string): PolygonVertices[] | undefined;
/**
* Return `true` if the hit-box cover the whole tile.
* @param tag The tag to allow collision layer filtering.
* @returns `true` if the hit-box cover the whole tile.
*/
hasFullHitBox(tag: string): boolean;
/**
* Animated tiles have a limitation:
* they are only able to use frames arranged horizontally one next

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"TileMapManager.d.ts","sourceRoot":"","sources":["../../src/render/TileMapManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAEjE;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,mBAAmB,CAAkC;;IAO7D;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc;IAWzD;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,kBAAkB,GAAG,IAAI;IAwBrD;;;;;;;OAOG;IACH,gBAAgB,CACd,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,KAC9D,IAAI,EACT,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,EACT,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,GAClD,IAAI;IAiCP,sBAAsB,CACpB,iBAAiB,EAAE,yBAAyB,EAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,MAAM,EAC1B,eAAe,EAAE,MAAM,EAGvB,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAC3C,IAAI;IAeP;;;;;;;;OAQG;IACH,qBAAqB,CACnB,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,KAC9D,IAAI,EACT,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpE,sBAAsB,EAAE,MAAM,EAC9B,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,GACxD,IAAI;IAwCP;;;;;;;OAOG;IACH,kCAAkC,CAChC,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpE,sBAAsB,EAAE,MAAM,EAC9B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,GACxD,IAAI;IAqBP,WAAW,IAAI,IAAI;CAIpB"}
{"version":3,"file":"TileMapManager.d.ts","sourceRoot":"","sources":["../../src/render/TileMapManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAEjE;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,mBAAmB,CAAkC;;IAO7D;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc;IAWzD;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,kBAAkB,GAAG,IAAI;IAwBrD;;;;;;;OAOG;IACH,gBAAgB,CACd,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,KAC9D,IAAI,EACT,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,GAAG,EACT,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,GAClD,IAAI;IAiCP,sBAAsB,CACpB,iBAAiB,EAAE,yBAAyB,EAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,MAAM,EAC1B,eAAe,EAAE,MAAM,EAGvB,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAC3C,IAAI;IAYP;;;;;;;;OAQG;IACH,qBAAqB,CACnB,WAAW,EAAE,CACX,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,KAC9D,IAAI,EACT,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpE,sBAAsB,EAAE,MAAM,EAC9B,uBAAuB,EAAE,MAAM,EAC/B,uBAAuB,EAAE,MAAM,EAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,GACxD,IAAI;IAwCP;;;;;;;OAOG;IACH,kCAAkC,CAChC,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpE,sBAAsB,EAAE,MAAM,EAC9B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,GACxD,IAAI;IAwBP,WAAW,IAAI,IAAI;CAIpB"}

View File

@@ -231,12 +231,16 @@ namespace gdjs {
);
return;
}
tileDefinition.addHitBox(this._hitBoxTag, [
[0, 0],
[0, tileMap.getTileHeight()],
[tileMap.getTileWidth(), tileMap.getTileHeight()],
[tileMap.getTileWidth(), 0],
]);
tileDefinition.addHitBox(
this._hitBoxTag,
[
[0, 0],
[0, tileMap.getTileHeight()],
[tileMap.getTileWidth(), tileMap.getTileHeight()],
[tileMap.getTileWidth(), 0],
],
true
);
});
this._tileMapManager.getOrLoadSimpleTileMapTextureCache(
@@ -624,7 +628,38 @@ namespace gdjs {
columnIndex: integer,
rowIndex: integer
) {
this._renderer.setTileId(columnIndex, rowIndex, 0, tileId);
const tileMap = this._renderer._tileMap;
if (!tileMap) {
return;
}
const layer = tileMap.getTileLayer(this._layerIndex);
if (!layer) {
return;
}
const oldTileId = layer.getTileId(columnIndex, rowIndex);
if (tileId === oldTileId) {
return;
}
layer.setTile(columnIndex, rowIndex, tileId);
if (this._collisionTileMap) {
const oldTileDefinition =
oldTileId !== undefined && tileMap.getTileDefinition(oldTileId);
const newTileDefinition = tileMap.getTileDefinition(tileId);
const hadFullHitBox =
!!oldTileDefinition &&
oldTileDefinition.hasFullHitBox(this._hitBoxTag);
const haveFullHitBox =
!!newTileDefinition &&
newTileDefinition.hasFullHitBox(this._hitBoxTag);
if (hadFullHitBox !== haveFullHitBox) {
this._collisionTileMap.invalidateTile(
this._layerIndex,
columnIndex,
rowIndex
);
}
}
this._isTileMapDirty = true;
this.invalidateHitboxes();
}
@@ -702,7 +737,26 @@ namespace gdjs {
}
removeTileAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
this._renderer.removeTile(columnIndex, rowIndex, 0);
const tileMap = this._renderer._tileMap;
if (!tileMap) {
return;
}
const layer = tileMap.getTileLayer(this._layerIndex);
if (!layer) {
return;
}
const oldTileId = layer.getTileId(columnIndex, rowIndex);
if (oldTileId === undefined) {
return;
}
layer.removeTile(columnIndex, rowIndex);
if (this._collisionTileMap) {
this._collisionTileMap.invalidateTile(
this._layerIndex,
columnIndex,
rowIndex
);
}
this._isTileMapDirty = true;
this.invalidateHitboxes();
}

View File

@@ -10,7 +10,8 @@ namespace gdjs {
private _object:
| gdjs.TileMapRuntimeObject
| gdjs.SimpleTileMapRuntimeObject;
private _tileMap: TileMapHelper.EditableTileMap | null = null;
// TODO Move this attribute in the object as it's a model.
_tileMap: TileMapHelper.EditableTileMap | null = null;
private _pixiObject: PIXI.tilemap.CompositeTilemap;
@@ -173,18 +174,6 @@ namespace gdjs {
return tileMap.getTileId(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param tileId The tile's id.
*/
setTileId(x: integer, y: integer, layerIndex: integer, tileId: number) {
const tileMap = this._tileMap;
if (!tileMap) return;
return tileMap.setTile(x, y, layerIndex, tileId);
}
/**
* @param x The layer column.
* @param y The layer row.
@@ -231,17 +220,6 @@ namespace gdjs {
return tileMap.isTileFlippedOnY(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
removeTile(x: integer, y: integer, layerIndex: integer) {
const tileMap = this._tileMap;
if (!tileMap) return;
return tileMap.removeTile(x, y, layerIndex);
}
/**
* @param targetRowCount The number of rows to have.
*/

View File

@@ -623,6 +623,7 @@ module.exports = {
tweenBehavior,
new gd.BehaviorsSharedData()
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js');

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