Compare commits

..

1 Commits

Author SHA1 Message Date
Davy Hélard
2a03c8902d Fix a lost of focus when a condition is pasted 2024-07-26 15:53:31 +02:00
532 changed files with 10289 additions and 21913 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,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,police,mouse,passwordjoyaxis,camera,objectPtr,forceMultiplier|}', '${2:Parameter description}', '${3:Optional parameter data}', /*parameterIsOptional=*/${4|false,true|})"
]
},
"Add code only parameter": {

View File

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

@@ -116,13 +116,6 @@ void SpriteAnimationList::ExposeResources(gd::ArbitraryResourceWorker& worker) {
}
}
bool SpriteAnimationList::HasAnimationNamed(const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const Animation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
const Animation& SpriteAnimationList::GetAnimation(std::size_t nb) const {
if (nb >= animations.size()) return badAnimation;

View File

@@ -51,11 +51,6 @@ class GD_CORE_API SpriteAnimationList {
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
/**
* \brief Return true if an animation exists for a given name.
*/
bool HasAnimationNamed(const gd::String &name) const;
/**
* \brief Add an animation at the end of the existing ones.
*/

View File

@@ -24,23 +24,18 @@
namespace gd {
SpriteObject::SpriteObject()
: updateIfNotVisible(false),
preScale(1) {}
: updateIfNotVisible(false) {}
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);
}
@@ -92,19 +87,6 @@ bool SpriteObject::UpdateInitialInstanceProperty(
return true;
}
size_t SpriteObject::GetAnimationsCount() const {
return animations.GetAnimationsCount();
}
const gd::String &SpriteObject::GetAnimationName(size_t index) const {
return animations.GetAnimation(index).GetName();
}
bool SpriteObject::HasAnimationNamed(
const gd::String &name) const {
return animations.HasAnimationNamed(name);
}
const SpriteAnimationList& SpriteObject::GetAnimations() const {
return animations;
}

View File

@@ -52,12 +52,6 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
const gd::String& name,
const gd::String& value) override;
size_t GetAnimationsCount() const override;
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Return the animation configuration.
*/
@@ -82,23 +76,6 @@ 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;
@@ -109,7 +86,6 @@ 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,8 +37,7 @@ BehaviorMetadata::BehaviorMetadata(
className(className_),
iconFilename(icon24x24),
instance(instance_),
sharedDatasInstance(sharedDatasInstance_),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {
sharedDatasInstance(sharedDatasInstance_) {
SetFullName(gd::String(fullname_));
SetDescription(gd::String(description_));
SetDefaultName(gd::String(defaultName_));
@@ -425,7 +424,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,7 +12,6 @@
#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;
@@ -42,10 +41,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(
@@ -298,18 +297,9 @@ 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.
*/
@@ -327,7 +317,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.
*/
@@ -384,7 +374,6 @@ 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) {
parameters.AddNewParameter("")
.SetType(type)
.SetDescription(description)
.SetCodeOnly(false)
.SetOptional(parameterIsOptional)
.SetExtraInfo(
gd::ParameterMetadata info;
info.SetType(type);
info.description = description;
info.codeOnly = false;
info.SetOptional(parameterIsOptional);
info.SetExtraInfo(
// For objects/behavior, the supplementary information
// parameter is an object/behavior type...
((gd::ParameterMetadata::IsObject(type) ||
@@ -59,16 +59,22 @@ 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) {
parameters.AddNewParameter("").SetType(type).SetCodeOnly().SetExtraInfo(
supplementaryInformation);
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);
return *this;
}
gd::ExpressionMetadata& ExpressionMetadata::SetRequiresBaseObjectCapability(
const gd::String& capability) {
requiredBaseObjectCapability = capability;

View File

@@ -193,9 +193,8 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
* \see AddParameter
*/
ExpressionMetadata &SetDefaultValue(const gd::String &defaultValue) override {
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetDefaultValue(defaultValue);
}
if (!parameters.empty())
parameters.back().SetDefaultValue(defaultValue);
return *this;
};
@@ -207,9 +206,8 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
*/
ExpressionMetadata &
SetParameterLongDescription(const gd::String &longDescription) override {
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetLongDescription(longDescription);
}
if (!parameters.empty())
parameters.back().SetLongDescription(longDescription);
return *this;
};
@@ -222,9 +220,7 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
*/
ExpressionMetadata &SetParameterExtraInfo(
const gd::String &extraInfo) override {
if (parameters.GetParametersCount() > 0) {
parameters.GetInternalVector().back()->SetExtraInfo(extraInfo);
}
if (!parameters.empty()) parameters.back().SetExtraInfo(extraInfo);
return *this;
}
@@ -252,16 +248,19 @@ 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.GetParameter(id);
return parameters[id];
};
gd::ParameterMetadata& GetParameter(std::size_t id) {
return parameters.GetParameter(id);
return parameters[id];
};
std::size_t GetParametersCount() const { return parameters.GetParametersCount(); };
const gd::ParameterMetadataContainer& GetParameters() const {
std::size_t GetParametersCount() const { return parameters.size(); };
const std::vector<gd::ParameterMetadata>& 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
@@ -369,8 +368,6 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
bool isPrivate;
gd::String requiredBaseObjectCapability;
gd::String relevantContext;
gd::ParameterMetadataContainer parameters;
};
} // namespace gd

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,8 @@
* reserved. This project is released under the MIT License.
*/
#pragma once
#ifndef PARAMETER_METADATA_H
#define PARAMETER_METADATA_H
#include <map>
#include <memory>
@@ -29,12 +29,6 @@ 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.
*/
@@ -254,3 +248,5 @@ class GD_CORE_API ParameterMetadata {
};
} // namespace gd
#endif // PARAMETER_METADATA_H

View File

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

View File

@@ -15,7 +15,6 @@ class ObjectsContainer;
class ObjectsContainersList;
class ParameterMetadata;
class Expression;
class ParameterMetadataContainer;
struct FunctionCallNode;
struct ExpressionNode;
} // namespace gd
@@ -25,20 +24,20 @@ class GD_CORE_API ParameterMetadataTools {
public:
static void ParametersToObjectsContainer(
const gd::Project& project,
const ParameterMetadataContainer& parameters,
const std::vector<gd::ParameterMetadata>& parameters,
gd::ObjectsContainer& outputObjectsContainer);
static void ForEachParameterMatchingSearch(
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb);
static bool Has(
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName);
static const gd::ParameterMetadata& Get(
const std::vector<const ParameterMetadataContainer*>& parametersVectorsList,
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName);
/**
@@ -48,7 +47,7 @@ class GD_CORE_API ParameterMetadataTools {
*/
static void IterateOverParameters(
const std::vector<gd::Expression>& parameters,
const ParameterMetadataContainer& parametersMetadata,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
const gd::String& lastObjectName)> fn);
@@ -60,7 +59,7 @@ class GD_CORE_API ParameterMetadataTools {
*/
static void IterateOverParametersWithIndex(
const std::vector<gd::Expression>& parameters,
const ParameterMetadataContainer& parametersMetadata,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
@@ -85,7 +84,7 @@ class GD_CORE_API ParameterMetadataTools {
* it's linked to.
*/
static size_t GetObjectParameterIndexFor(
const ParameterMetadataContainer& parametersMetadata,
const std::vector<gd::ParameterMetadata>& 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 == "audioResource" ||
parameterType == "videoResource" ||
parameterType == "soundfile" ||
parameterType == "musicfile" ||
parameterType == "bitmapFontResource" ||
parameterType == "imageResource" ||
parameterType == "jsonResource" ||
@@ -219,10 +219,7 @@ class GD_CORE_API ValueTypeMetadata {
parameterType == "tilesetResource" ||
parameterType == "model3DResource" ||
parameterType == "atlasResource" ||
parameterType == "spineResource" ||
// Deprecated, old parameter types:
parameterType == "soundfile" ||
parameterType == "musicfile";
parameterType == "spineResource";
}
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.GetParametersCount(); ++pNb) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
if (ParameterMetadata::IsExpression(
"number", instrInfos.parameters.GetParameter(pNb).GetType()) ||
"number", instrInfos.parameters[pNb].GetType()) ||
ParameterMetadata::IsExpression(
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
"string", instrInfos.parameters[pNb].GetType())) {
auto node = instruction.GetParameter(pNb).GetRootNode();
node->Visit(*this);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -163,11 +163,6 @@ void GroupVariableHelper::ApplyChangesToObjects(
variablesContainer.Remove(variableName);
}
for (const gd::String &variableName : changeset.addedVariableNames) {
if (variablesContainer.Has(variableName)) {
// It can happens if an object already had the variable but it was not
// shared by other object of the group.
continue;
}
variablesContainer.Insert(variableName,
groupVariablesContainer.Get(variableName),
variablesContainer.Count());
@@ -175,13 +170,7 @@ void GroupVariableHelper::ApplyChangesToObjects(
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
if (variablesContainer.Has(newVariableName)) {
// It can happens if an object already had the variable but it was not
// shared by other object of the group.
variablesContainer.Remove(oldVariableName);
} else {
variablesContainer.Rename(oldVariableName, newVariableName);
}
variablesContainer.Rename(oldVariableName, newVariableName);
}
// Apply type and value changes
for (const gd::String &variableName : changeset.valueChangedVariableNames) {

View File

@@ -225,7 +225,9 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
size_t parameterIndex,
const gd::String& lastObjectName) {
const String& parameterValue = parameterExpression.GetPlainString();
if (parameterMetadata.GetType() == "fontResource") {
if (parameterMetadata.GetType() ==
"police" || // Should be renamed fontResource
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().GetInternalVector()) {
if (gd::ParameterMetadata::IsBehavior(parameter->GetType()) &&
parameter->GetExtraInfo() == oldBehaviorType) {
parameter->SetExtraInfo(newBehaviorType);
for (auto &&parameter : eventsFunction.GetParameters()) {
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().GetInternalVector()) {
if (gd::ParameterMetadata::IsObject(parameter->GetType()) &&
parameter->GetExtraInfo() == oldObjectType) {
parameter->SetExtraInfo(newObjectType);
for (auto &&parameter : eventsFunction.GetParameters()) {
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().AddParameter(objectParameter);
setter.GetParameters().push_back(objectParameter);
if (isBehavior) {
gd::ParameterMetadata behaviorParameter;
gd::String behaviorFullType =
@@ -163,7 +163,7 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
.SetName("Behavior")
.SetDescription("Behavior")
.SetExtraInfo(behaviorFullType);
setter.GetParameters().AddParameter(behaviorParameter);
setter.GetParameters().push_back(behaviorParameter);
}
gd::ParameterMetadata valueParameter;
valueParameter.SetType("yesorno")
@@ -171,7 +171,7 @@ void PropertyFunctionGenerator::GenerateGetterAndSetter(
.SetDescription(capitalizedName)
.SetOptional(true)
.SetDefaultValue("yes");
setter.GetParameters().AddParameter(valueParameter);
setter.GetParameters().push_back(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.GetParametersCount() < 2) {
while (parameters.size() < 2) {
gd::ParameterMetadata newParameter;
parameters.AddParameter(newParameter);
parameters.push_back(newParameter);
}
parameters.GetParameter(0)
parameters[0]
.SetType("object")
.SetName(behaviorObjectParameterName)
.SetDescription("Object")
.SetExtraInfo(eventsBasedBehavior.GetObjectType());
parameters.GetParameter(1)
parameters[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.GetParametersCount() < 1) {
while (parameters.size() < 1) {
gd::ParameterMetadata newParameter;
parameters.AddParameter(newParameter);
parameters.push_back(newParameter);
}
parameters.GetParameter(0)
parameters[0]
.SetType("object")
.SetName(parentObjectParameterName)
.SetDescription("Object")

View File

@@ -9,7 +9,6 @@
#include <map>
#include <memory>
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Project/QuickCustomization.h"
#include "GDCore/String.h"
namespace gd {
@@ -32,10 +31,10 @@ namespace gd {
*/
class GD_CORE_API BehaviorConfigurationContainer {
public:
BehaviorConfigurationContainer() : folded(false), quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
BehaviorConfigurationContainer() : folded(false){};
BehaviorConfigurationContainer(const gd::String& name_,
const gd::String& type_)
: name(name_), type(type_), folded(false), quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
: name(name_), type(type_), folded(false){};
virtual ~BehaviorConfigurationContainer();
virtual BehaviorConfigurationContainer* Clone() const { return new BehaviorConfigurationContainer(*this); }
@@ -115,13 +114,6 @@ class GD_CORE_API BehaviorConfigurationContainer {
*/
bool IsFolded() const { return folded; }
void SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
quickCustomizationVisibility = visibility;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
protected:
/**
@@ -168,7 +160,6 @@ 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 objectProperties = std::map<gd::String, gd::PropertyDescriptor>();
auto behaviorProperties = std::map<gd::String, gd::PropertyDescriptor>();
for (auto &property : properties.GetInternalVector()) {
const auto &propertyName = property->GetName();
const auto &propertyType = property->GetType();
// Copy the property
objectProperties[propertyName] = *property;
behaviorProperties[propertyName] = *property;
auto &newProperty = objectProperties[propertyName];
auto &newProperty = behaviorProperties[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 objectProperties;
return behaviorProperties;
}
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,9 +21,6 @@ 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();
@@ -45,26 +42,6 @@ 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) {
@@ -78,18 +55,6 @@ 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(
@@ -102,7 +67,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>();
@@ -163,14 +128,26 @@ void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const
animations.SerializeTo(animatableElement);
}
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);
}
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());
}
}
void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
@@ -182,16 +159,12 @@ void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
animations.UnserializeFrom(animatableElement);
}
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);
}
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);
}
}
@@ -251,20 +224,6 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
}
}
std::size_t CustomObjectConfiguration::GetAnimationsCount() const {
return animations.GetAnimationsCount();
}
const gd::String &
CustomObjectConfiguration::GetAnimationName(size_t index) const {
return animations.GetAnimation(index).GetName();
}
bool CustomObjectConfiguration::HasAnimationNamed(
const gd::String &name) const {
return animations.HasAnimationNamed(name);
}
const SpriteAnimationList& CustomObjectConfiguration::GetAnimations() const {
return animations;
}

View File

@@ -30,7 +30,7 @@ namespace gd {
class CustomObjectConfiguration : public gd::ObjectConfiguration {
public:
CustomObjectConfiguration(const Project& project_, const String& type_)
: project(&project_), isMarkedAsOverridingEventsBasedObjectChildrenConfiguration(false) {
: project(&project_) {
SetType(type_);
}
std::unique_ptr<gd::ObjectConfiguration> Clone() const override;
@@ -65,28 +65,7 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
void ExposeResources(gd::ArbitraryResourceWorker& worker) override;
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;
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
gd::ObjectConfiguration &GetChildObjectConfiguration(const gd::String& objectName);
/**
* \brief Return the animation configuration for Animatable custom objects.
@@ -105,14 +84,10 @@ 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;
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration;
mutable std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;
std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;
static gd::ObjectConfiguration badObjectConfiguration;

View File

@@ -4,7 +4,6 @@
* 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"
@@ -13,10 +12,9 @@ namespace gd {
EventsBasedBehavior::EventsBasedBehavior()
: AbstractEventsBasedEntity(
"MyBehavior", gd::EventsFunctionsContainer::FunctionOwner::Behavior),
sharedPropertyDescriptors(
gd::EventsFunctionsContainer::FunctionOwner::Behavior),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
"MyBehavior",
gd::EventsFunctionsContainer::FunctionOwner::Behavior),
sharedPropertyDescriptors(gd::EventsFunctionsContainer::FunctionOwner::Behavior) {}
void EventsBasedBehavior::SerializeTo(SerializerElement& element) const {
AbstractEventsBasedEntity::SerializeTo(element);
@@ -26,13 +24,6 @@ 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,
@@ -42,14 +33,6 @@ 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,7 +11,6 @@
#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;
@@ -89,15 +88,6 @@ 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.
*/
@@ -151,7 +141,6 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
gd::String objectType;
bool isPrivate = false;
gd::PropertiesContainer sharedPropertyDescriptors;
QuickCustomization::Visibility quickCustomizationVisibility;
};
} // namespace gd

View File

@@ -16,7 +16,6 @@ EventsBasedObject::EventsBasedObject()
isRenderedIn3D(false),
isAnimatable(false),
isTextContainer(false),
isInnerAreaFollowingParentSize(false),
areaMinX(0),
areaMinY(0),
areaMinZ(0),
@@ -38,9 +37,6 @@ 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);
@@ -63,8 +59,6 @@ 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,35 +101,11 @@ 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.
*/
@@ -303,7 +279,6 @@ 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 gd::ParameterMetadataContainer &EventsFunction::GetParametersForEvents(
const gd::EventsFunctionsContainer &functionsContainer) const {
const std::vector<gd::ParameterMetadata>& 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.ClearParameters();
actionWithOperationParameters.clear();
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.GetParametersCount();
i++) {
actionWithOperationParameters.AddParameter(
expressionParameters.GetParameter(i));
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]);
}
gd::ParameterMetadata parameterMetadata;
parameterMetadata.SetName("Value").SetValueTypeMetadata(
expression.expressionType);
actionWithOperationParameters.AddParameter(parameterMetadata);
parameterMetadata.SetName("Value").SetValueTypeMetadata(expression.expressionType);
actionWithOperationParameters.push_back(parameterMetadata);
for (size_t i = expressionValueParameterIndex;
i < expressionParameters.GetParametersCount(); i++) {
actionWithOperationParameters.AddParameter(
expressionParameters.GetParameter(i));
i < expressionParameters.size();
i++)
{
actionWithOperationParameters.push_back(expressionParameters[i]);
}
return actionWithOperationParameters;
@@ -101,7 +101,10 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
expressionType.SerializeTo(element.AddChild("expressionType"));
}
gd::SerializerElement& parametersElement = element.AddChild("parameters");
parameters.SerializeParametersTo(parametersElement);
parametersElement.ConsiderAsArrayOf("parameter");
for (const auto& parameter : parameters) {
parameter.SerializeTo(parametersElement.AddChild("parameter"));
}
objectGroups.SerializeTo(element.AddChild("objectGroups"));
}
@@ -143,9 +146,15 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
else
functionType = Action;
const gd::SerializerElement &parametersElement =
const gd::SerializerElement& parametersElement =
element.GetChild("parameters");
parameters.UnserializeParametersFrom(parametersElement);
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);
}
objectGroups.UnserializeFrom(element.GetChild("objectGroups"));
}

View File

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

View File

@@ -14,7 +14,6 @@
#include "GDCore/Tools/Log.h"
namespace gd {
gd::String ObjectConfiguration::badAnimationName;
ObjectConfiguration::~ObjectConfiguration() {}

View File

@@ -165,36 +165,7 @@ class GD_CORE_API ObjectConfiguration {
void UnserializeFrom(gd::Project& project, const SerializerElement& element);
///@}
/** \name Animations
* Members functions related to object animations
*/
///@{
/**
* \brief Return the number of animations declared in this object
* configuration.
*/
virtual size_t GetAnimationsCount() const {
return 0;
};
/**
* \brief Return the name of an animation declared in this object
* configuration.
*/
virtual const gd::String &GetAnimationName(size_t index) const {
return badAnimationName;
}
/**
* \brief Return true if an animation is declared in this object
* configuration for a given name.
*/
virtual bool HasAnimationNamed(const gd::String &animationName) const {
return false;
}
///@}
protected:
protected:
gd::String type; ///< Which type of object is represented by this
///< configuration.
@@ -210,9 +181,6 @@ protected:
* custom attributes.
*/
virtual void DoSerializeTo(SerializerElement& element) const {};
private:
static gd::String badAnimationName;
};
} // namespace gd

View File

@@ -19,20 +19,13 @@ namespace gd {
ObjectFolderOrObject ObjectFolderOrObject::badObjectFolderOrObject;
ObjectFolderOrObject::ObjectFolderOrObject()
: folderName("__NULL"),
object(nullptr),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
: folderName("__NULL"), object(nullptr) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::String folderName_,
ObjectFolderOrObject* parent_)
: folderName(folderName_),
parent(parent_),
object(nullptr),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
: folderName(folderName_), parent(parent_), object(nullptr) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::Object* object_,
ObjectFolderOrObject* parent_)
: object(object_),
parent(parent_),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
: object(object_), parent(parent_) {}
ObjectFolderOrObject::~ObjectFolderOrObject() {}
bool ObjectFolderOrObject::HasObjectNamed(const gd::String& name) {
@@ -73,8 +66,7 @@ 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];
}
@@ -214,14 +206,6 @@ 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(
@@ -259,15 +243,6 @@ 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,7 +10,6 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Project/QuickCustomization.h"
namespace gd {
class Project;
@@ -167,11 +166,6 @@ 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.
*/
@@ -194,7 +188,6 @@ 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

@@ -73,15 +73,6 @@ bool ObjectsContainersList::HasObjectNamed(const gd::String& name) const {
return false;
}
const gd::Object* ObjectsContainersList::GetObject(const gd::String& name) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(name)) return &(*it)->GetObject(name);
}
return nullptr;
}
ObjectsContainersList::VariableExistence
ObjectsContainersList::HasObjectOrGroupWithVariableNamed(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
@@ -377,7 +368,7 @@ std::vector<gd::String> ObjectsContainersList::ExpandObjectName(
}
// Ensure that all returned objects actually exists (i.e: if some groups have
// names referring to non existing objects, don't return them).
// names refering to non existing objects, don't return them).
for (std::size_t i = 0; i < realObjects.size();) {
if (!HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
@@ -530,72 +521,4 @@ std::vector<gd::String> ObjectsContainersList::GetBehaviorsOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetAnimationNamesOfObject(
const gd::String &objectOrGroupName) const {
std::vector<gd::String> animationNames;
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto &objectConfiguration =
(*it)->GetObject(objectOrGroupName).GetConfiguration();
for (size_t index = 0; index < objectConfiguration.GetAnimationsCount();
index++) {
animationNames.push_back(objectConfiguration.GetAnimationName(index));
}
return animationNames;
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
const auto &objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto &objectNames = objectGroup.GetAllObjectsNames();
std::size_t objectIndex = 0;
bool isFirstObjectFound = false;
for (; objectIndex < objectNames.size() && !isFirstObjectFound;
objectIndex++) {
const gd::String &objectName = objectNames[objectIndex];
if (!HasObjectNamed(objectName)) {
continue;
}
isFirstObjectFound = true;
const auto &objectConfiguration =
GetObject(objectName)->GetConfiguration();
for (size_t index = 0; index < objectConfiguration.GetAnimationsCount();
index++) {
animationNames.push_back(objectConfiguration.GetAnimationName(index));
}
}
for (; objectIndex < objectNames.size(); objectIndex++) {
const gd::String &objectName = objectNames[objectIndex];
if (!HasObjectNamed(objectName)) {
continue;
}
const auto &objectConfiguration =
GetObject(objectName)->GetConfiguration();
for (size_t animationIndex = 0; animationIndex < animationNames.size();
animationIndex++) {
if (!objectConfiguration.HasAnimationNamed(
animationNames[animationIndex])) {
animationNames.erase(animationNames.begin() + animationIndex);
animationIndex--;
}
}
}
return animationNames;
}
}
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

@@ -129,17 +129,7 @@ class GD_CORE_API ObjectsContainersList {
const gd::String& objectName, bool searchInGroups = true) const;
/**
* \brief Get the animation names of an object/group.
* \note The animation names of a group are the animation names common to
* every object of the group.
*
* @return The names of animations
*/
std::vector<gd::String>
GetAnimationNamesOfObject(const gd::String &objectOrGroupName) const;
/**
* \brief Return a list containing all objects referred to by the group.
* \brief Return a list containing all objects refered to by the group.
* If an object name is passed, then only this object name is returned.
*
* If \a onlyObjectToSelectIfPresent is set and present in the group(s),
@@ -173,16 +163,6 @@ 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(){};
@@ -190,8 +170,6 @@ class GD_CORE_API ObjectsContainersList {
private:
bool HasObjectNamed(const gd::String& name) const;
const gd::Object* GetObject(const gd::String& name) const;
bool HasObjectWithVariableNamed(const gd::String& objectName,
const gd::String& variableName) const;

View File

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

View File

@@ -38,13 +38,6 @@ 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) {
@@ -74,21 +67,11 @@ 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;
if (element.HasChild("quickCustomizationVisibility")) {
quickCustomizationVisibility =
element.GetChild("quickCustomizationVisibility").GetStringValue() ==
"visible"
? QuickCustomization::Visibility::Visible
: QuickCustomization::Visibility::Hidden;
} else {
quickCustomizationVisibility = QuickCustomization::Visibility::Default;
}
? element.GetChild("advanced").GetBoolValue()
: false;
}
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {

View File

@@ -9,7 +9,6 @@
#include "GDCore/String.h"
#include "GDCore/Project/MeasurementUnit.h"
#include "GDCore/Project/QuickCustomization.h"
namespace gd {
class SerializerElement;
@@ -33,16 +32,14 @@ class GD_CORE_API PropertyDescriptor {
PropertyDescriptor(gd::String propertyValue)
: currentValue(propertyValue), type("string"), label(""), hidden(false),
deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
/**
* \brief Empty constructor creating an empty property to be displayed.
*/
PropertyDescriptor()
: hidden(false), deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()),
quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
measurementUnit(gd::MeasurementUnit::GetUndefined()){};
/**
* \brief Destructor
@@ -112,7 +109,7 @@ class GD_CORE_API PropertyDescriptor {
extraInformation.push_back(info);
return *this;
}
/**
* \brief Change the unit of measurement of the property value.
*/
@@ -131,7 +128,7 @@ class GD_CORE_API PropertyDescriptor {
const std::vector<gd::String>& GetExtraInfo() const {
return extraInformation;
}
std::vector<gd::String>& GetExtraInfo() {
return extraInformation;
}
@@ -175,13 +172,6 @@ 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
*/
///@{
@@ -222,7 +212,6 @@ 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

@@ -1,16 +0,0 @@
#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 == variable.children.end()) {
if (it == children.end()) {
return false;
}
auto &otherChild = it->second;

View File

@@ -323,7 +323,6 @@ 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.
*/

View File

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

View File

@@ -0,0 +1,8 @@
// 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,6 +24,15 @@ 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,6 +46,21 @@ 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,9 +527,15 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
}
SECTION("Parameters (1 level)") {
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyParameter1", 0).SetType("number");
parameters.InsertNewParameter("MyParameter2", 1).SetType("string");
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);
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);
@@ -562,10 +568,19 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
}
}
SECTION("Parameters (1 level, number|string)") {
gd::ParameterMetadataContainer parameters;
parameters.InsertNewParameter("MyNumberParameter", 0).SetType("number");
parameters.InsertNewParameter("MyStringParameter", 1).SetType("string");
parameters.InsertNewParameter("MyBooleanParameter", 2).SetType("yesorno");
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);
auto projectScopedContainersWithParameters = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithParameters.AddParameters(parameters);

View File

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

136
Core/tests/Layout.cpp Normal file
View File

@@ -0,0 +1,136 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering layout content helper methods.
*/
#include "GDCore/Project/Layout.h"
#include "DummyPlatform.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "catch.hpp"
using namespace gd;
TEST_CASE("Layout", "[common]") {
SECTION("Find the type of a behavior in a object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyObject", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION("Give an empty type for an object that doesn't have the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyObject", "MyBehavior", true) == "");
}
SECTION("Find the type of a behavior in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION(
"Give an empty type for a group with an object missing the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
// object2 doesn't have the behavior.
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for a group with behaviors of same name but "
"different types") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyBehavior");
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for an empty group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group =
layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
REQUIRE(GetTypeOfBehaviorInObjectOrGroup(
project.GetObjects(), layout.GetObjects(),
"MyGroup", "MyBehavior", true) == "");
}
}

View File

@@ -1,488 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering layout content helper methods.
*/
#include "GDCore/Project/ObjectsContainersList.h"
#include "DummyPlatform.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/MakeUnique.h"
#include "catch.hpp"
#include <algorithm>
#include <vector>
using namespace gd;
TEST_CASE("ObjectContainersList (HasObjectOrGroupNamed)", "[common]") {
SECTION("Can check an object exists") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupNamed("MyObject"));
REQUIRE(!objectsContainersList.HasObjectOrGroupNamed("MyWrongObject"));
}
SECTION("Can check a group exists") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupNamed("MyGroup"));
}
}
TEST_CASE("ObjectContainersList (HasObjectOrGroupWithVariableNamed)", "[common]") {
SECTION("Can check a variable exists in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.GetVariables().InsertNew("MyVariable", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupWithVariableNamed("MyObject", "MyVariable"));
REQUIRE(!objectsContainersList.HasObjectOrGroupWithVariableNamed("MyObject", "MyWrongVariable"));
}
SECTION("Can check a variable exists in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.GetVariables().InsertNew("MyVariable", 0);
// This variable is only in one of the 2 objects.
object1.GetVariables().InsertNew("MyOtherVariable", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object1.GetVariables().InsertNew("MyVariable", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasObjectOrGroupWithVariableNamed("MyGroup", "MyVariable"));
REQUIRE(!objectsContainersList.HasObjectOrGroupWithVariableNamed("MyGroup", "MyWrongVariable"));
}
}
TEST_CASE("ObjectContainersList (GetTypeOfObject)", "[common]") {
SECTION("Find the type of an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject("MyObject") == "MyExtension::Sprite");
}
SECTION("Find the object type of a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject("MyGroup") == "MyExtension::Sprite");
}
SECTION("Give an empty type for groups with mixed object types") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "FakeObjectWithDefaultBehavior", "MyObject2", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject("MyGroup") == "");
}
SECTION("Give an empty type for an empty group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfObject(
"MyGroup") == "");
}
}
TEST_CASE("ObjectContainersList (GetTypeOfBehaviorInObjectOrGroup)",
"[common]") {
SECTION("Find the type of a behavior in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyObject", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION("Give an empty type for an object that doesn't have the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyObject", "MyBehavior", true) == "");
}
SECTION("Find the type of a behavior in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "MyExtension::MyBehavior");
}
SECTION(
"Give an empty type for a group with an object missing the behavior") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
// object2 doesn't have the behavior.
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for a group with behaviors of same name but different types") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "");
}
SECTION("Give an empty type for an empty group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
"MyGroup", "MyBehavior", true) == "");
}
}
TEST_CASE("ObjectContainersList (HasBehaviorInObjectOrGroup)", "[common]") {
SECTION("Can check a behavior exists in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasBehaviorInObjectOrGroup("MyObject", "MyBehavior"));
REQUIRE(!objectsContainersList.HasBehaviorInObjectOrGroup("MyObject", "MyWrongBehavior"));
}
SECTION("Can check a behavior exists in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
// This behavior is only in one of the 2 objects.
object1.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyOtherBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
REQUIRE(objectsContainersList.HasBehaviorInObjectOrGroup("MyGroup", "MyBehavior"));
REQUIRE(!objectsContainersList.HasBehaviorInObjectOrGroup("MyGroup", "MyOtherBehavior"));
}
}
TEST_CASE("ObjectContainersList (GetBehaviorsOfObject)", "[common]") {
SECTION("Find the behaviors in an object") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto behaviors =
objectsContainersList.GetBehaviorsOfObject("MyObject", true);
REQUIRE(behaviors.size() == 1);
REQUIRE(behaviors[0] == "MyBehavior");
}
SECTION("Find the behaviors in a group") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
// This behavior is only in one of the 2 objects.
object1.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyOtherBehavior");
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject2", 0);
object2.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto behaviors =
objectsContainersList.GetBehaviorsOfObject("MyGroup", true);
REQUIRE(behaviors.size() == 1);
REQUIRE(behaviors[0] == "MyBehavior");
}
}
namespace {
gd::SpriteObject BuildSpriteWithAnimations(gd::String animationName1 = "!",
gd::String animationName2 = "!",
gd::String animationName3 = "!") {
gd::SpriteObject configuration;
gd::SpriteAnimationList &animations = configuration.GetAnimations();
if (animationName1 != "!") {
gd::Animation animation;
animation.SetName(animationName1);
animations.AddAnimation(animation);
if (animationName2 != "!") {
gd::Animation animation;
animation.SetName(animationName2);
animations.AddAnimation(animation);
}
if (animationName3 != "!") {
gd::Animation animation;
animation.SetName(animationName3);
animations.AddAnimation(animation);
}
}
return configuration;
}
bool Contains(const std::vector<gd::String> &vector, const gd::String &value) {
return std::find(vector.begin(), vector.end(), value) !=
vector.end();
}
} // namespace
TEST_CASE("ObjectContainersList (GetAnimationNamesOfObject)", "[common]") {
SECTION("Find the animation names in a sprite") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object object("MyObject", "Sprite",
gd::make_unique<gd::SpriteObject>(
BuildSpriteWithAnimations("Idle", "Run")));
layout.GetObjects().InsertObject(object, 0);
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto animationNames =
objectsContainersList.GetAnimationNamesOfObject("MyObject");
REQUIRE(Contains(animationNames, "Idle"));
REQUIRE(Contains(animationNames, "Run"));
REQUIRE(animationNames.size() == 2);
}
SECTION("Find the animation names in a group of sprite") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object object1("MyObject1", "Sprite",
gd::make_unique<gd::SpriteObject>(
BuildSpriteWithAnimations("Idle", "Jump", "Run")));
layout.GetObjects().InsertObject(object1, 0);
gd::Object object2("MyObject2", "Sprite",
gd::make_unique<gd::SpriteObject>(
BuildSpriteWithAnimations("Run", "Idle", "Climb")));
layout.GetObjects().InsertObject(object2, 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());
group.AddObject(object2.GetName());
auto objectsContainersList = gd::ObjectsContainersList::
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
const auto animationNames =
objectsContainersList.GetAnimationNamesOfObject("MyGroup");
REQUIRE(Contains(animationNames, "Idle"));
REQUIRE(Contains(animationNames, "Run"));
REQUIRE(animationNames.size() == 2);
}
}

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().GetParametersCount() == 0);
REQUIRE(getter.GetParameters().size() == 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().GetParametersCount() == 0);
REQUIRE(setter.GetParameters().size() == 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().GetParametersCount() == 0);
REQUIRE(getter.GetParameters().size() == 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().GetParametersCount() == 3);
auto &objectParameter = setter.GetParameters().GetParameter(0);
REQUIRE(setter.GetParameters().size() == 3);
auto &objectParameter = setter.GetParameters().at(0);
REQUIRE(objectParameter.GetName() == "Object");
REQUIRE(objectParameter.GetType() == "object");
auto &behaviorParameter = setter.GetParameters().GetParameter(1);
auto &behaviorParameter = setter.GetParameters().at(1);
REQUIRE(behaviorParameter.GetName() == "Behavior");
REQUIRE(behaviorParameter.GetType() == "behavior");
REQUIRE(behaviorParameter.GetExtraInfo() ==
"MyEventsExtension::MyEventsBasedBehavior");
auto &valueParameter = setter.GetParameters().GetParameter(2);
auto &valueParameter = setter.GetParameters().at(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().GetParametersCount() == 0);
REQUIRE(getter.GetParameters().size() == 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().GetParametersCount() == 0);
REQUIRE(setter.GetParameters().size() == 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().GetParametersCount() == 0);
REQUIRE(getter.GetParameters().size() == 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().GetParametersCount() == 2);
auto &objectParameter = setter.GetParameters().GetParameter(0);
REQUIRE(setter.GetParameters().size() == 2);
auto &objectParameter = setter.GetParameters().at(0);
REQUIRE(objectParameter.GetName() == "Object");
REQUIRE(objectParameter.GetType() == "object");
REQUIRE(objectParameter.GetExtraInfo() ==
"MyEventsExtension::MyEventsBasedObject");
auto &valueParameter = setter.GetParameters().GetParameter(1);
auto &valueParameter = setter.GetParameters().at(1);
REQUIRE(valueParameter.GetName() == "Value");
REQUIRE(valueParameter.GetType() == "yesorno");

View File

@@ -2684,55 +2684,6 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
456);
}
SECTION("Can add a group variable when one of the object already has it") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &scene = project.InsertNewLayout("Scene", 0);
auto &object = scene.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "Object", 0);
// This variable is only in one object.
object.GetVariables().InsertNew("MyGroupVariable").SetValue(123);
auto &otherObject = scene.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "OtherObject", 0);
auto &group = scene.GetObjects().GetObjectGroups().InsertNew("Group");
group.AddObject("Object");
group.AddObject("OtherObject");
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project, scene);
gd::VariablesContainer groupVariables =
gd::GroupVariableHelper::MergeVariableContainers(
projectScopedContainers.GetObjectsContainersList(), group);
REQUIRE(groupVariables.Count() == 0);
// Do the changes and launch the refactoring.
groupVariables.ResetPersistentUuid();
gd::SerializerElement originalSerializedVariables;
groupVariables.SerializeTo(originalSerializedVariables);
groupVariables.InsertNew("MyGroupVariable", 0).SetValue(456);
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
originalSerializedVariables, groupVariables);
REQUIRE(changeset.addedVariableNames.size() == 1);
gd::WholeProjectRefactorer::ApplyRefactoringForGroupVariablesContainer(
project, project.GetObjects(), scene.GetObjects(), groupVariables,
group, changeset, originalSerializedVariables);
// The variable kept its original value.
REQUIRE(object.GetVariables().Count() == 1);
REQUIRE(object.GetVariables().Get("MyGroupVariable").GetValue() == 123);
REQUIRE(otherObject.GetVariables().Count() == 1);
REQUIRE(otherObject.GetVariables().Get("MyGroupVariable").GetValue() ==
456);
}
SECTION("Can rename a group variable") {
gd::Project project;
gd::Platform platform;
@@ -2808,114 +2759,6 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
project, project.GetObjects(), scene.GetObjects(), groupVariables,
group, changeset, originalSerializedVariables);
REQUIRE(object.GetVariables().Count() == 1);
REQUIRE(object.GetVariables().Get("MyRenamedGroupVariable").GetValue() == 123);
REQUIRE(otherObject.GetVariables().Count() == 1);
REQUIRE(otherObject.GetVariables().Get("MyRenamedGroupVariable").GetValue() ==
123);
REQUIRE(event.GetActions()[0].GetParameter(1).GetPlainString() ==
"MyRenamedGroupVariable");
REQUIRE(event.GetActions()[0].GetParameter(3).GetPlainString() ==
"Group.MyRenamedGroupVariable");
REQUIRE(event.GetActions()[1].GetParameter(1).GetPlainString() ==
"MyRenamedGroupVariable");
REQUIRE(event.GetActions()[1].GetParameter(3).GetPlainString() ==
"Object.MyRenamedGroupVariable");
REQUIRE(event.GetActions()[2].GetParameter(1).GetPlainString() ==
"MyRenamedGroupVariable");
REQUIRE(event.GetActions()[2].GetParameter(3).GetPlainString() ==
"OtherObject.MyRenamedGroupVariable");
}
SECTION("Can rename a group variable when one of the object already has it") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &scene = project.InsertNewLayout("Scene", 0);
auto &object = scene.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "Object", 0);
object.GetVariables().InsertNew("MyGroupVariable").SetValue(123);
// This variable is only in one object.
object.GetVariables().InsertNew("MyRenamedGroupVariable").SetValue(111);
auto &otherObject = scene.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "OtherObject", 0);
otherObject.GetVariables().InsertNew("MyGroupVariable").SetValue(123);
auto &group = scene.GetObjects().GetObjectGroups().InsertNew("Group");
group.AddObject("Object");
group.AddObject("OtherObject");
gd::StandardEvent &event =
dynamic_cast<gd::StandardEvent &>(scene.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
{
gd::Instruction action;
action.SetType("SetNumberObjectVariable");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("Group"));
action.SetParameter(1, gd::Expression("MyGroupVariable"));
action.SetParameter(2, gd::Expression("="));
action.SetParameter(3, gd::Expression("Group.MyGroupVariable"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("SetNumberObjectVariable");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("Object"));
action.SetParameter(1, gd::Expression("MyGroupVariable"));
action.SetParameter(2, gd::Expression("="));
action.SetParameter(3, gd::Expression("Object.MyGroupVariable"));
event.GetActions().Insert(action);
}
{
gd::Instruction action;
action.SetType("SetNumberObjectVariable");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("OtherObject"));
action.SetParameter(1, gd::Expression("MyGroupVariable"));
action.SetParameter(2, gd::Expression("="));
action.SetParameter(3, gd::Expression("OtherObject.MyGroupVariable"));
event.GetActions().Insert(action);
}
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project, scene);
gd::VariablesContainer groupVariables =
gd::GroupVariableHelper::MergeVariableContainers(
projectScopedContainers.GetObjectsContainersList(), group);
REQUIRE(groupVariables.Has("MyGroupVariable"));
REQUIRE(groupVariables.Get("MyGroupVariable").GetValue() == 123);
// Do the changes and launch the refactoring.
groupVariables.ResetPersistentUuid();
gd::SerializerElement originalSerializedVariables;
groupVariables.SerializeTo(originalSerializedVariables);
groupVariables.Rename("MyGroupVariable", "MyRenamedGroupVariable");
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
originalSerializedVariables, groupVariables);
REQUIRE(changeset.oldToNewVariableNames.size() == 1);
gd::WholeProjectRefactorer::ApplyRefactoringForGroupVariablesContainer(
project, project.GetObjects(), scene.GetObjects(), groupVariables,
group, changeset, originalSerializedVariables);
// The variable kept its original value.
REQUIRE(object.GetVariables().Count() == 1);
REQUIRE(object.GetVariables().Get("MyRenamedGroupVariable").GetValue() == 111);
REQUIRE(otherObject.GetVariables().Count() == 1);
REQUIRE(otherObject.GetVariables().Get("MyRenamedGroupVariable").GetValue() ==
123);
REQUIRE(event.GetActions()[0].GetParameter(1).GetPlainString() ==
"MyRenamedGroupVariable");
REQUIRE(event.GetActions()[0].GetParameter(3).GetPlainString() ==

View File

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

View File

@@ -304,15 +304,8 @@ namespace gdjs {
*/
setDepth(depth: float): void {
const unscaledDepth = this.getUnscaledDepth();
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);
if (unscaledDepth !== 0) {
this.setScaleZ(depth / unscaledDepth);
}
}
@@ -332,10 +325,6 @@ 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

@@ -235,19 +235,6 @@ void Model3DObjectConfiguration::ExposeResources(
worker.ExposeModel3D(modelResourceName);
}
const gd::String &
Model3DObjectConfiguration::GetAnimationName(size_t index) const {
return GetAnimation(index).GetName();
}
bool Model3DObjectConfiguration::HasAnimationNamed(
const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const Model3DAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
Model3DAnimation Model3DObjectConfiguration::badAnimation;
const Model3DAnimation &
@@ -265,6 +252,14 @@ Model3DAnimation &Model3DObjectConfiguration::GetAnimation(std::size_t nb) {
return animations[nb];
}
bool Model3DObjectConfiguration::HasAnimationNamed(
const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const Model3DAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
void Model3DObjectConfiguration::AddAnimation(
const Model3DAnimation &animation) {
animations.push_back(animation);

View File

@@ -85,12 +85,6 @@ public:
* Methods related to animations management
*/
///@{
std::size_t GetAnimationsCount() const override { return animations.size(); };
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Return the animation at the specified index.
* If the index is out of bound, a "bad animation" object is returned.
@@ -103,6 +97,16 @@ public:
*/
Model3DAnimation &GetAnimation(std::size_t nb);
/**
* \brief Return the number of animations this object has.
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
/**
* \brief Return true if the animation called "name" exists.
*/
bool HasAnimationNamed(const gd::String& name) const;
/**
* \brief Add an animation at the end of the existing ones.
*/

View File

@@ -118,19 +118,6 @@ 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(
@@ -138,6 +125,9 @@ namespace gdjs {
this._animations[0].loop
);
}
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
}
updateFromObjectData(
@@ -145,21 +135,7 @@ namespace gdjs {
newObjectData: Model3DObjectData
): boolean {
super.updateFromObjectData(oldObjectData, newObjectData);
if (
oldObjectData.content.materialType !==
newObjectData.content.materialType
) {
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 ||
@@ -167,12 +143,19 @@ namespace gdjs {
oldObjectData.content.rotationY !== newObjectData.content.rotationY ||
oldObjectData.content.rotationZ !== newObjectData.content.rotationZ ||
oldObjectData.content.keepAspectRatio !==
newObjectData.content.keepAspectRatio ||
oldObjectData.content.materialType !==
newObjectData.content.materialType
newObjectData.content.keepAspectRatio
) {
this._updateModel(newObjectData);
}
if (
oldObjectData.content.materialType !==
newObjectData.content.materialType
) {
this._materialType = this._convertMaterialType(
newObjectData.content.materialType
);
this._updateModel(newObjectData);
}
if (
oldObjectData.content.originLocation !==
newObjectData.content.originLocation
@@ -235,12 +218,6 @@ 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,20 +236,6 @@ 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,7 +333,6 @@ namespace gdjs {
adUnitId,
position: atTop ? 'top' : 'bottom',
size: bannerRequestedAdSizeType,
offset: 0,
});
banner.on('load', () => {

View File

@@ -25,6 +25,7 @@ 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)
@@ -33,8 +34,6 @@ 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");
}
@@ -46,8 +45,6 @@ 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");
}
@@ -72,7 +69,6 @@ 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."));
@@ -83,7 +79,6 @@ 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."));
@@ -94,7 +89,6 @@ 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."));
@@ -105,7 +99,6 @@ 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."));
@@ -134,8 +127,6 @@ 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;
}
@@ -148,8 +139,6 @@ 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;
}
@@ -183,3 +172,4 @@ 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.
*/
#pragma once
#ifndef ANCHORBEHAVIOR_H
#define ANCHORBEHAVIOR_H
#include <vector>
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Object.h"
@@ -22,16 +22,14 @@ 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_WINDOW_CENTER = 4
ANCHOR_HORIZONTAL_PROPORTIONAL = 3
};
enum VerticalAnchor {
ANCHOR_VERTICAL_NONE = 0,
ANCHOR_VERTICAL_WINDOW_TOP = 1,
ANCHOR_VERTICAL_WINDOW_BOTTOM = 2,
ANCHOR_VERTICAL_PROPORTIONAL = 3,
ANCHOR_VERTICAL_WINDOW_CENTER = 4
ANCHOR_VERTICAL_PROPORTIONAL = 3
};
AnchorBehavior() {};
@@ -49,3 +47,5 @@ class GD_EXTENSION_API AnchorBehavior : public gd::Behavior {
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override;
};
#endif // ANCHORBEHAVIOR_H

View File

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

View File

@@ -4,25 +4,10 @@ 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: HorizontalAnchor;
_rightEdgeAnchor: HorizontalAnchor;
_leftEdgeAnchor: any;
_rightEdgeAnchor: any;
_topEdgeAnchor: any;
_bottomEdgeAnchor: any;
_invalidDistances: boolean = true;
@@ -89,25 +74,14 @@ namespace gdjs {
gdjs.AnchorRuntimeBehavior.prototype.doStepPreEvents
) as FloatPoint;
// TODO EBO Make it work with event based objects or hide this behavior for them.
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 game = instanceContainer.getGame();
let rendererWidth = game.getGameResolutionWidth();
let rendererHeight = game.getGameResolutionHeight();
const layer = instanceContainer.getLayer(this.owner.getLayer());
if (this._invalidDistances) {
if (this._relativeToOriginalWindowSize) {
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;
rendererWidth = game.getOriginalWidth();
rendererHeight = game.getOriginalHeight();
}
//Calculate the distances from the window's bounds.
@@ -118,28 +92,49 @@ namespace gdjs {
workingPoint
);
// 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;
//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;
}
}
}
// 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;
//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;
}
}
}
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
const bottomRightPixel = layer.convertCoords(
this.owner.getDrawableX() + this.owner.getWidth(),
@@ -148,30 +143,49 @@ namespace gdjs {
workingPoint
);
// 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;
//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;
}
}
}
// 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;
//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;
}
}
}
this._invalidDistances = false;
} else {
//Move and resize the object if needed
@@ -180,50 +194,93 @@ namespace gdjs {
let rightPixel = 0;
let bottomPixel = 0;
// 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;
//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;
}
}
}
// 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;
//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;
}
}
}
// 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;
//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;
}
}
}
// 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;
//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;
}
}
}
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
const topLeftCoord = layer.convertInverseCoords(
leftPixel,
@@ -246,18 +303,27 @@ namespace gdjs {
// Compatibility with GD <= 5.0.133
if (this._useLegacyBottomAndRightAnchors) {
//Move and resize the object according to the anchors
if (this._rightEdgeAnchor !== HorizontalAnchor.None) {
if (
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setWidth(right - left);
}
if (this._bottomEdgeAnchor !== VerticalAnchor.None) {
if (
this._bottomEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setHeight(bottom - top);
}
if (this._leftEdgeAnchor !== HorizontalAnchor.None) {
if (
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setX(
left + this.owner.getX() - this.owner.getDrawableX()
);
}
if (this._topEdgeAnchor !== VerticalAnchor.None) {
if (
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setY(
top + this.owner.getY() - this.owner.getDrawableY()
);
@@ -267,18 +333,25 @@ namespace gdjs {
else {
// Resize if right and left anchors are set
if (
this._rightEdgeAnchor !== HorizontalAnchor.None &&
this._leftEdgeAnchor !== HorizontalAnchor.None
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE &&
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setWidth(right - left);
this.owner.setX(left);
} else {
if (this._leftEdgeAnchor !== HorizontalAnchor.None) {
if (
this._leftEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setX(
left + this.owner.getX() - this.owner.getDrawableX()
);
}
if (this._rightEdgeAnchor !== HorizontalAnchor.None) {
if (
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setX(
right +
this.owner.getX() -
@@ -289,18 +362,24 @@ namespace gdjs {
}
// Resize if top and bottom anchors are set
if (
this._bottomEdgeAnchor !== VerticalAnchor.None &&
this._topEdgeAnchor !== VerticalAnchor.None
this._bottomEdgeAnchor !==
AnchorRuntimeBehavior.VerticalAnchor.NONE &&
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setHeight(bottom - top);
this.owner.setY(top);
} else {
if (this._topEdgeAnchor !== VerticalAnchor.None) {
if (
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setY(
top + this.owner.getY() - this.owner.getDrawableY()
);
}
if (this._bottomEdgeAnchor !== VerticalAnchor.None) {
if (
this._bottomEdgeAnchor !==
AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setY(
bottom +
this.owner.getY() -
@@ -314,6 +393,19 @@ 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,13 +36,6 @@ 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',
@@ -74,10 +67,13 @@ 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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -87,36 +83,29 @@ 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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -125,10 +114,13 @@ 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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(1000);
expect(object.getY()).to.equal(500);
@@ -140,10 +132,13 @@ 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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -153,36 +148,29 @@ 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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(500);
@@ -191,10 +179,13 @@ 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);
setGameResolutionSizeAndStep(2000, 2000);
runtimeGame.setGameResolutionSize(2000, 2000);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.equal(500);
expect(object.getY()).to.equal(1000);

View File

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

View File

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

View File

@@ -369,36 +369,6 @@ 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',
@@ -422,13 +392,13 @@ module.exports = {
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.isCurrentPlayerHost');
.setFunctionName('gdjs.multiplayer.isPlayerHost');
extension
.addCondition(
'HasAnyPlayerLeft',
_('Any player has left'),
_('Check if any player has left the lobby game.'),
_('Check if any player has left the lobby.'),
_('Any player has left'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
@@ -453,7 +423,7 @@ module.exports = {
.addCondition(
'HasPlayerLeft',
_('Player has left'),
_('Check if the player has left the lobby game.'),
_('Check if the player has left the lobby.'),
_('Player _PARAM0_ has left'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
@@ -478,10 +448,8 @@ module.exports = {
extension
.addExpression(
'LastLeftPlayerNumber',
_('Player number that just left'),
_(
'Returns the player number of the player that has just left the lobby.'
),
_('Last left player number'),
_('Returns the number of the player that has just left the lobby.'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg'
)
@@ -556,10 +524,8 @@ module.exports = {
extension
.addExpression(
'LastJoinedPlayerNumber',
_('Player number that just joined'),
_(
'Returns the player number of the player that has just joined the lobby.'
),
_('Last joined player number'),
_('Returns the number of the player that has just joined the lobby.'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg'
)
@@ -580,61 +546,6 @@ 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',
@@ -1086,7 +997,6 @@ 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 sceneSyncDataSyncRate = 1;
const sceneSyncDataTickRate = 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 gameSyncDataSyncRate = 1;
const gameSyncDataTickRate = 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 heartbeatSyncRate = 1;
let lastHeartbeatSentTimestamp = 0;
const heartbeatTickRate = 1;
let lastHeartbeatTimestamp = 0;
let _playersLastRoundTripTimes: {
[playerNumber: number]: number[];
} = {};
@@ -531,10 +531,7 @@ 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.isCurrentPlayerHost() &&
!ownershipChangeIsCoherent
) {
if (gdjs.multiplayer.isPlayerHost() && !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.
@@ -563,7 +560,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
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(
@@ -741,7 +738,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const otherPeerIds = connectedPeerIds.filter(
(peerId) => peerId !== messageSender
@@ -866,10 +863,7 @@ 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.isCurrentPlayerHost() &&
!ownershipChangeIsCoherent
) {
if (gdjs.multiplayer.isPlayerHost() && !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.
@@ -898,7 +892,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
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(
@@ -1342,7 +1336,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
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(
@@ -1435,7 +1429,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
const messagesList = gdjs.multiplayerPeerJsHelper.getOrCreateMessagesList(
messageName
);
@@ -1598,7 +1592,7 @@ namespace gdjs {
// If we are the host,
// so we need to relay the message to others.
if (gdjs.multiplayer.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
// 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();
@@ -1656,7 +1650,7 @@ namespace gdjs {
const hasSceneBeenSyncedRecently = () => {
return (
getTimeNow() - lastSceneSyncTimestamp < 1000 / sceneSyncDataSyncRate
getTimeNow() - lastSceneSyncTimestamp < 1000 / sceneSyncDataTickRate
);
};
@@ -1670,7 +1664,6 @@ namespace gdjs {
const sceneNetworkSyncData = runtimeScene.getNetworkSyncData({
playerNumber: gdjs.multiplayer.getCurrentPlayerNumber(),
isHost: gdjs.multiplayer.isCurrentPlayerHost(),
});
if (!sceneNetworkSyncData) {
return;
@@ -1744,7 +1737,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
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(
@@ -1821,7 +1814,7 @@ namespace gdjs {
};
const hasGameBeenSyncedRecently = () => {
return getTimeNow() - lastGameSyncTimestamp < 1000 / gameSyncDataSyncRate;
return getTimeNow() - lastGameSyncTimestamp < 1000 / gameSyncDataTickRate;
};
const handleUpdateGameMessagesToSend = (
@@ -1834,7 +1827,6 @@ namespace gdjs {
const gameNetworkSyncData = runtimeScene.getGame().getNetworkSyncData({
playerNumber: gdjs.multiplayer.getCurrentPlayerNumber(),
isHost: gdjs.multiplayer.isCurrentPlayerHost(),
});
if (!gameNetworkSyncData) {
return;
@@ -1896,7 +1888,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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
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(
@@ -1945,10 +1937,9 @@ namespace gdjs {
messageName: string;
messageData: any;
} => {
// 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.
// 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.
playerId: gdjs.playerAuthentication.getUserId(),
username: gdjs.playerAuthentication.getUsername(),
};
@@ -1985,15 +1976,15 @@ namespace gdjs {
};
const hasSentHeartbeatRecently = () => {
return (
!!lastHeartbeatSentTimestamp &&
getTimeNow() - lastHeartbeatSentTimestamp < 1000 / heartbeatSyncRate
!!lastHeartbeatTimestamp &&
getTimeNow() - lastHeartbeatTimestamp < 1000 / heartbeatTickRate
);
};
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.isCurrentPlayerHost()) {
if (!gdjs.multiplayer.isPlayerHost()) {
return;
}
@@ -2006,7 +1997,7 @@ namespace gdjs {
const { messageName, messageData } = createHeartbeatMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
lastHeartbeatSentTimestamp = getTimeNow();
lastHeartbeatTimestamp = getTimeNow();
};
const handleHeartbeatsReceived = () => {
@@ -2033,7 +2024,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.isCurrentPlayerHost()) {
if (!gdjs.multiplayer.isPlayerHost()) {
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
const currentlyKnownPlayerNumbers = Object.keys(
_playersInfo
@@ -2143,20 +2134,12 @@ namespace gdjs {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const { messageName, messageData } = createHeartbeatMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
lastHeartbeatSentTimestamp = getTimeNow();
lastHeartbeatTimestamp = 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) {
@@ -2170,15 +2153,7 @@ namespace gdjs {
return getPlayerPing(currentPlayerNumber);
};
const markPlayerAsDisconnected = ({
runtimeScene,
playerNumber,
peerId,
}: {
runtimeScene: gdjs.RuntimeScene;
playerNumber: number;
peerId?: string;
}) => {
const markPlayerAsDisconnected = (playerNumber: number) => {
logger.info(`Marking player ${playerNumber} as disconnected.`);
_playerNumbersWhoJustLeft.push(playerNumber);
// Temporarily save the username in another variable to be used for the notification,
@@ -2186,31 +2161,23 @@ namespace gdjs {
_temporaryPlayerNumberToUsername[playerNumber] = getPlayerUsername(
playerNumber
);
clearPlayerTempData(playerNumber);
// 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;
}
// 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;
}
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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const { messageName, messageData } = createHeartbeatMessage();
sendDataTo(connectedPeerIds, messageName, messageData);
lastHeartbeatSentTimestamp = getTimeNow();
lastHeartbeatTimestamp = getTimeNow();
}
};
@@ -2233,10 +2200,7 @@ namespace gdjs {
}
// We rely on the p2p helper to know who has disconnected.
const justDisconnectedPlayers: {
playerNumber: number;
peerId: string;
}[] = [];
const justDisconnectedPlayerNumbers: number[] = [];
const justDisconnectedPeers = gdjs.multiplayerPeerJsHelper.getJustDisconnectedPeers();
if (justDisconnectedPeers.length) {
@@ -2248,17 +2212,14 @@ namespace gdjs {
return;
}
logger.info(`Player ${disconnectedPlayerNumber} has disconnected.`);
justDisconnectedPlayers.push({
playerNumber: disconnectedPlayerNumber,
peerId: disconnectedPeer,
});
justDisconnectedPlayerNumbers.push(disconnectedPlayerNumber);
}
}
for (const { playerNumber, peerId } of justDisconnectedPlayers) {
for (const playerNumber of justDisconnectedPlayerNumbers) {
// 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.isCurrentPlayerHost()) {
if (gdjs.multiplayer.isPlayerHost()) {
const instances = runtimeScene.getAdhocListOfAllInstances();
for (const instance of instances) {
const behavior = instance.getBehavior(
@@ -2282,7 +2243,7 @@ namespace gdjs {
}
}
markPlayerAsDisconnected({ runtimeScene, playerNumber, peerId });
markPlayerAsDisconnected(playerNumber);
}
};
@@ -2342,10 +2303,6 @@ namespace gdjs {
return _playersInfo[playerNumber] !== undefined;
};
const getPlayersInfo = () => {
return _playersInfo;
};
const endGameMessageName = '#endGame';
const createEndGameMessage = (): {
messageName: string;
@@ -2358,7 +2315,7 @@ namespace gdjs {
};
const sendEndGameMessage = () => {
// Only the host can end the game.
if (!gdjs.multiplayer.isCurrentPlayerHost()) {
if (!gdjs.multiplayer.isPlayerHost()) {
return;
}
@@ -2370,8 +2327,8 @@ namespace gdjs {
sendDataTo(connectedPeerIds, messageName, messageData);
};
const handleEndGameMessagesReceived = () => {
if (gdjs.multiplayer.isCurrentPlayerHost()) {
const handleEndGameMessages = () => {
if (gdjs.multiplayer.isPlayerHost()) {
// Only other players need to react to the end game message.
return;
}
@@ -2391,50 +2348,6 @@ 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 = {};
@@ -2496,7 +2409,6 @@ namespace gdjs {
// Heartbeats.
handleHeartbeatsToSend,
handleHeartbeatsReceived,
hasReceivedHeartbeatFromPlayer,
// Pings & usernames.
getPlayerPing,
getCurrentPlayerPing,
@@ -2507,14 +2419,12 @@ namespace gdjs {
getConnectedPlayers,
getNumberOfConnectedPlayers,
isPlayerConnected,
getPlayersInfo,
// Leaving players.
hasAnyPlayerJustLeft,
hasPlayerJustLeft,
getPlayersWhoJustLeft,
getLatestPlayerWhoJustLeft,
removePlayerWhoJustLeft,
markPlayerAsDisconnected,
// Joining players.
hasAnyPlayerJustJoined,
hasPlayerJustJoined,
@@ -2523,11 +2433,8 @@ namespace gdjs {
removePlayerWhoJustJoined,
// End game.
sendEndGameMessage,
handleEndGameMessagesReceived,
handleEndGameMessages,
clearAllMessagesTempData,
// Resume game after migration.
sendResumeGameMessage,
handleResumeGameMessagesReceived,
};
};

View File

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

View File

@@ -24,46 +24,48 @@ namespace gdjs {
actionOnPlayerDisconnect: string;
// The last time the object has been synchronized.
// This is to avoid synchronizing the object too often, see _objectMaxSyncRate.
// This is to avoid synchronizing the object too often, see _objectMaxTickRate.
_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.
_objectBasicInfoSyncRate: number = 5;
_objectBasicInfoTickRate: 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 SyncRate for a number of times to ensure they are received, without the need of an acknowledgment.
// on the max tickrate 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.
_variablesSyncRate: number = 1;
_variablesTickRate: 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 syncRate as the object update for a number of times
// on the same tickrate 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.
_effectsSyncRate: number = 1;
_effectsTickRate: 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 syncRate as the object update for a number of times
// on the same tickrate 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;
_logSyncRate: number = 1;
_logTickRate: 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;
@@ -129,35 +131,35 @@ namespace gdjs {
}
private _hasObjectBeenSyncedWithinMaxRate() {
const objectMaxSyncRate = gdjs.multiplayer.getObjectsSynchronizationRate();
return (
getTimeNow() - this._lastObjectSyncTimestamp < 1000 / objectMaxSyncRate
getTimeNow() - this._lastObjectSyncTimestamp <
1000 / this._objectMaxTickRate
);
}
private _hasObjectBasicInfoBeenSyncedRecently() {
return (
getTimeNow() - this._lastBasicObjectSyncTimestamp <
1000 / this._objectBasicInfoSyncRate
1000 / this._objectBasicInfoTickRate
);
}
private _haveVariablesBeenSyncedRecently() {
return (
getTimeNow() - this._lastVariablesSyncTimestamp <
1000 / this._variablesSyncRate
1000 / this._variablesTickRate
);
}
private _haveEffectsBeenSyncedRecently() {
return (
getTimeNow() - this._lastEffectsSyncTimestamp <
1000 / this._effectsSyncRate
1000 / this._effectsTickRate
);
}
// private _logToConsoleWithThrottle(message: string) {
// if (getTimeNow() - this._lastLogTimestamp > 1000 / this._logSyncRate) {
// if (getTimeNow() - this._lastLogTimestamp > 1000 / this._logTickRate) {
// logger.info(message);
// this._lastLogTimestamp = getTimeNow();
// }
@@ -413,10 +415,7 @@ 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.isCurrentPlayerHost()
) {
if (!this._isOwnerAsPlayerOrHost() && !gdjs.multiplayer.isPlayerHost()) {
return;
}

View File

@@ -1,82 +1,6 @@
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;
@@ -93,38 +17,20 @@ 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 _websocketHeartbeatIntervalFunction: NodeJS.Timeout | null = null;
let _lobbyHeartbeatIntervalFunction: NodeJS.Timeout | null = null;
let _websocketHeartbeatInterval: NodeJS.Timeout | null = null;
let _lobbyHeartbeatInterval: NodeJS.Timeout | null = null;
const DEFAULT_WEBSOCKET_HEARTBEAT_INTERVAL = 10000;
const DEFAULT_LOBBY_HEARTBEAT_INTERVAL = 30000;
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;
const DEFAULT_COUNTDOWN_SECONDS_TO_START = 5;
// 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) => {
@@ -182,11 +88,6 @@ 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
);
@@ -260,19 +161,6 @@ 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.
@@ -293,7 +181,7 @@ namespace gdjs {
/**
* Returns the number of players in the lobby.
*/
export const getPlayersInLobbyCount = (): number => {
export const getPlayersInLobbyCount = () => {
// 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();
@@ -302,7 +190,7 @@ namespace gdjs {
/**
* Returns true if the player at this position is connected to the lobby.
*/
export const isPlayerConnected = (playerNumber: number): boolean => {
export const isPlayerConnected = (playerNumber: number) => {
return gdjs.multiplayerMessageManager.isPlayerConnected(playerNumber);
};
@@ -311,52 +199,29 @@ 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 = (): number => {
export const getCurrentPlayerNumber = () => {
return playerNumber || 0;
};
/**
* Returns true if the player is the host in the lobby.
* This can change during the game.
* Returns true if the player is the host in the lobby. Here, player 1.
*/
export const isCurrentPlayerHost = (): boolean => {
return (
!!hostPeerId &&
hostPeerId === gdjs.multiplayerPeerJsHelper.getCurrentId()
);
export const isPlayerHost = () => {
return playerNumber === 1;
};
/**
* 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): string => {
export const getPlayerUsername = (playerNumber: number) => {
return gdjs.multiplayerMessageManager.getPlayerUsername(playerNumber);
};
/**
* Returns the player username of the current player in the lobby.
*/
export const getCurrentPlayerUsername = (): string => {
export const getCurrentPlayerUsername = () => {
const currentPlayerNumber = getCurrentPlayerNumber();
return getPlayerUsername(currentPlayerNumber);
};
@@ -376,12 +241,7 @@ 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.
if (
isCurrentPlayerHost() &&
isReadyToSendOrReceiveGameUpdateMessages()
) {
sendHeartbeatToBackend();
}
sendHeartbeatToBackend();
}
};
@@ -393,15 +253,6 @@ 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,
@@ -459,7 +310,6 @@ namespace gdjs {
_websocket.close();
_connectionId = null;
playerNumber = null;
hostPeerId = null;
_lobbyId = null;
_websocket = null;
}
@@ -489,7 +339,7 @@ namespace gdjs {
_websocket.onopen = () => {
logger.info('Connected to the lobby.');
// Register a heartbeat to keep the connection alive.
_websocketHeartbeatIntervalFunction = setInterval(() => {
_websocketHeartbeatInterval = setInterval(() => {
if (_websocket) {
_websocket.send(
JSON.stringify({
@@ -563,21 +413,23 @@ 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;
currentLobbyHeartbeatInterval =
const heartbeatInterval =
messageData.heartbeatInterval ||
DEFAULT_LOBBY_HEARTBEAT_INTERVAL;
handleGameStartedEvent({
runtimeScene,
});
handleGameStartedEvent({ runtimeScene, heartbeatInterval });
break;
}
case 'peerId': {
@@ -600,14 +452,14 @@ namespace gdjs {
}
};
_websocket.onclose = () => {
if (!_isLobbyGameRunning) {
logger.info('Disconnected from the lobby.');
}
logger.info(
'Disconnected from the lobby. Either manually or game started.'
);
_connectionId = null;
_websocket = null;
if (_websocketHeartbeatIntervalFunction) {
clearInterval(_websocketHeartbeatIntervalFunction);
if (_websocketHeartbeatInterval) {
clearInterval(_websocketHeartbeatInterval);
}
// If the game is running, then all good.
@@ -724,7 +576,6 @@ namespace gdjs {
}
_connectionId = null;
playerNumber = null;
hostPeerId = null;
_lobbyId = null;
_websocket = null;
};
@@ -762,14 +613,15 @@ 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, we are chosen as the host.
// We then send the peerId to others so they can connect via P2P.
// When the countdown starts, if we are player number 1, then send the peerId to others so they can connect via P2P.
if (getCurrentPlayerNumber() === 1) {
sendPeerId();
}
@@ -787,6 +639,7 @@ 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.
);
@@ -799,34 +652,43 @@ namespace gdjs {
const sendHeartbeatToBackend = async function () {
const gameId = gdjs.projectData.properties.projectUuid;
if (!gameId || !_lobbyId) {
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!gameId || !playerId || !playerToken || !_lobbyId) {
logger.error(
'Cannot keep the lobby playing without the game ID or lobby ID.'
'Cannot keep the lobby playing without the game ID or player ID.'
);
return;
}
const heartbeatRelativeUrl = `/play/game/${gameId}/public-lobby/${_lobbyId}/action/heartbeat`;
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 players = gdjs.multiplayerMessageManager.getConnectedPlayers();
try {
await fetchAsPlayer({
relativeUrl: heartbeatRelativeUrl,
await fetch(heartbeatUrl, {
method: 'POST',
headers,
body: JSON.stringify({
players,
}),
dev: isUsingGDevelopDevelopmentEnvironment,
});
} catch (error) {
logger.error('Error while sending heartbeat, retrying:', error);
try {
await fetchAsPlayer({
relativeUrl: heartbeatRelativeUrl,
await fetch(heartbeatUrl, {
method: 'POST',
headers,
body: JSON.stringify({
players,
}),
dev: isUsingGDevelopDevelopmentEnvironment,
});
} catch (error) {
logger.error(
@@ -843,14 +705,16 @@ 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 (!isCurrentPlayerHost() && allConnectedPeers.length === 0) {
if (!isPlayerHost() && allConnectedPeers.length === 0) {
gdjs.multiplayerComponents.displayConnectionErrorNotification(
runtimeScene
);
@@ -862,10 +726,10 @@ namespace gdjs {
}
// If we are the host, start pinging the backend to let it know the lobby is running.
if (isCurrentPlayerHost()) {
_lobbyHeartbeatIntervalFunction = setInterval(async () => {
if (isPlayerHost()) {
_lobbyHeartbeatInterval = setInterval(async () => {
await sendHeartbeatToBackend();
}, currentLobbyHeartbeatInterval);
}, heartbeatInterval);
}
// If we are connected to players, then the game can start.
@@ -893,11 +757,9 @@ namespace gdjs {
_isLobbyGameRunning = false;
_lobbyId = null;
playerNumber = null;
hostPeerId = null;
_isReadyToSendOrReceiveGameUpdateMessages = false;
if (_lobbyHeartbeatIntervalFunction) {
clearInterval(_lobbyHeartbeatIntervalFunction);
_lobbyHeartbeatIntervalFunction = null;
if (_lobbyHeartbeatInterval) {
clearInterval(_lobbyHeartbeatInterval);
}
// Disconnect from any P2P connections.
@@ -933,7 +795,6 @@ namespace gdjs {
return;
}
hostPeerId = peerId;
gdjs.multiplayerPeerJsHelper.connect(peerId);
};
@@ -1017,315 +878,10 @@ 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.
@@ -1335,7 +891,7 @@ namespace gdjs {
return;
}
if (!isCurrentPlayerHost()) {
if (!isPlayerHost()) {
logger.error('Only the host can end the game.');
return;
}
@@ -1350,18 +906,31 @@ namespace gdjs {
// Also call backend to end the game.
const gameId = gdjs.projectData.properties.projectUuid;
if (!gameId || !_lobbyId) {
logger.error('Cannot end the lobby without the game ID or lobby ID.');
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.');
return;
}
const endGameRelativeUrl = `/play/game/${gameId}/public-lobby/${_lobbyId}/action/end`;
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}`;
try {
await fetchAsPlayer({
relativeUrl: endGameRelativeUrl,
await fetch(endGameUrl, {
method: 'POST',
body: JSON.stringify({}),
dev: isUsingGDevelopDevelopmentEnvironment,
headers,
body: JSON.stringify({
gameId,
lobbyId: _lobbyId,
}),
});
} catch (error) {
logger.error('Error while ending the game:', error);
@@ -1397,8 +966,6 @@ namespace gdjs {
peerId,
})
);
// We are the host.
hostPeerId = peerId;
};
/**

View File

@@ -313,14 +313,9 @@ 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, isHost?: boolean }[]} players
* @param {{ playerNumber: number, peerId: string}[]} 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) {
@@ -330,9 +325,6 @@ 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.
@@ -403,16 +395,12 @@ 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', () => {
@@ -423,7 +411,7 @@ describe('Multiplayer', () => {
initiateGameWithPlayers,
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -619,7 +607,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -705,7 +693,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -842,7 +830,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1038,7 +1026,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -1093,11 +1081,13 @@ describe('Multiplayer', () => {
const {
object: p1SpriteObject,
behavior: p1SpriteObjectBehavior,
} = getObjectAndMultiplayerBehaviorsFromScene(
p1RuntimeScene,
'MySpriteObject'
)[0];
p1SpriteObjectBehavior._objectMaxTickRate = Infinity;
p1SpriteObject.setX(242);
p1SpriteObject.setY(243);
p1RuntimeScene.renderAndStep(1000 / 60);
@@ -1165,7 +1155,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1247,11 +1237,13 @@ describe('Multiplayer', () => {
const {
object: p2SpriteObject,
behavior: p2SpriteObjectBehavior,
} = getObjectAndMultiplayerBehaviorsFromScene(
p2RuntimeScene,
'MySpriteObject'
)[0];
p2SpriteObjectBehavior._objectMaxTickRate = Infinity;
p2SpriteObject.setX(242);
p2SpriteObject.setY(243);
p2RuntimeScene.renderAndStep(1000 / 60);
@@ -1366,7 +1358,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1638,7 +1630,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1848,7 +1840,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -1907,7 +1899,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -1949,7 +1941,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -2148,10 +2140,12 @@ describe('Multiplayer', () => {
const {
object: p2SpriteObject,
behavior: p2SpriteMultiplayerObjectBehavior,
} = getObjectAndMultiplayerBehaviorsFromScene(
p2RuntimeScene,
'MySpriteObject'
)[0];
p2SpriteMultiplayerObjectBehavior._objectMaxTickRate = Infinity;
p2SpriteObject.setX(242);
p2SpriteObject.setY(243);
p2RuntimeScene.renderAndStep(1000 / 60);
@@ -2208,7 +2202,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2371,7 +2365,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2456,7 +2450,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2588,7 +2582,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2682,7 +2676,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];
@@ -2691,7 +2685,7 @@ describe('Multiplayer', () => {
// Player 2 leaves.
const newConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 3, peerId: 'player-3' },
];
// Host sees the player 2 leaving.
@@ -2718,7 +2712,7 @@ describe('Multiplayer', () => {
} = createMultiplayerManagersMock();
const allConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 3, peerId: 'player-3' },
];
initiateGameWithPlayers(allConnectedPlayers);
@@ -2729,7 +2723,7 @@ describe('Multiplayer', () => {
// Player 2 joins.
const newConnectedPlayers = [
{ playerNumber: 1, peerId: 'player-1', isHost: true },
{ playerNumber: 1, peerId: 'player-1' },
{ playerNumber: 2, peerId: 'player-2' },
{ playerNumber: 3, peerId: 'player-3' },
];

View File

@@ -182,7 +182,6 @@ module.exports = {
.setValue(behaviorContent.getChild('bodyType').getStringValue())
.setType('Choice')
.setLabel('Type')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.addExtraInfo('Static')
.addExtraInfo('Dynamic')
.addExtraInfo('Kinematic');
@@ -191,7 +190,6 @@ module.exports = {
.setValue(
behaviorContent.getChild('bullet').getBoolValue() ? 'true' : 'false'
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setType('Boolean')
.setLabel('Bullet');
behaviorProperties
@@ -201,7 +199,6 @@ module.exports = {
? 'true'
: 'false'
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setType('Boolean')
.setLabel('Fixed Rotation');
behaviorProperties
@@ -209,7 +206,6 @@ module.exports = {
.setValue(
behaviorContent.getChild('canSleep').getBoolValue() ? 'true' : 'false'
)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.setType('Boolean')
.setLabel('Can Sleep');
behaviorProperties
@@ -217,7 +213,6 @@ module.exports = {
.setValue(behaviorContent.getChild('shape').getStringValue())
.setType('Choice')
.setLabel('Shape')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.addExtraInfo('Box')
.addExtraInfo('Circle')
.addExtraInfo('Edge')
@@ -232,8 +227,7 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Dimension A')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Shape Dimension A');
behaviorProperties
.getOrCreate('shapeDimensionB')
.setValue(
@@ -244,8 +238,7 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Dimension B')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Shape Dimension B');
behaviorProperties
.getOrCreate('shapeOffsetX')
.setValue(
@@ -253,8 +246,7 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Offset X')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Shape Offset X');
behaviorProperties
.getOrCreate('shapeOffsetY')
.setValue(
@@ -262,8 +254,7 @@ module.exports = {
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setLabel('Shape Offset Y')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Shape Offset Y');
behaviorProperties
.getOrCreate('polygonOrigin')
.setValue(
@@ -275,8 +266,7 @@ module.exports = {
.setLabel('Polygon Origin')
.addExtraInfo('Center')
.addExtraInfo('Origin')
.addExtraInfo('TopLeft')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.addExtraInfo('TopLeft');
behaviorProperties
.getOrCreate('vertices')
.setValue(
@@ -284,8 +274,7 @@ module.exports = {
? gd.Serializer.toJSON(behaviorContent.getChild('vertices'))
: '[]'
)
.setLabel('Vertices')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Vertices');
behaviorProperties
.getOrCreate('density')
.setValue(
@@ -326,28 +315,24 @@ module.exports = {
.toString(10)
)
.setType('Number')
.setLabel('Angular Damping')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Angular Damping');
behaviorProperties
.getOrCreate('gravityScale')
.setValue(
behaviorContent.getChild('gravityScale').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Gravity Scale')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Gravity Scale');
behaviorProperties
.getOrCreate('layers')
.setValue(behaviorContent.getChild('layers').getIntValue().toString(10))
.setType('Number')
.setLabel('Layers')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Layers');
behaviorProperties
.getOrCreate('masks')
.setValue(behaviorContent.getChild('masks').getIntValue().toString(10))
.setType('Number')
.setLabel('Masks')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
.setLabel('Masks');
return behaviorProperties;
};
@@ -394,15 +379,16 @@ module.exports = {
return true;
}
if (propertyName === 'worldScale') {
if (propertyName === 'scaleX') {
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;
}
@@ -426,22 +412,16 @@ 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('worldScale')
.getOrCreate('scaleX')
.setValue(
sharedContent.getChild('worldScale').getDoubleValue().toString(10)
sharedContent.getChild('scaleX').getDoubleValue().toString(10)
)
.setType('Number');
sharedProperties
.getOrCreate('scaleY')
.setValue(
sharedContent.getChild('scaleY').getDoubleValue().toString(10)
)
.setType('Number');
@@ -450,8 +430,6 @@ 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);
};
@@ -479,19 +457,6 @@ 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',
@@ -1777,7 +1742,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.'
@@ -1804,7 +1769,7 @@ module.exports = {
'Inertia',
_('Inertia'),
_(
'Return the rotational inertia of the object (in kilograms · meters²)'
'Return the rotational inertia of the object (in kilograms * meters * meters)'
),
'',
'res/physics32.png'

View File

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

View File

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

View File

@@ -61,7 +61,6 @@ 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())
@@ -80,7 +79,6 @@ PlatformerObjectBehavior::GetProperties(
behaviorContent.GetDoubleAttribute("maxFallingSpeed")));
properties["LadderClimbingSpeed"]
.SetLabel(_("Ladder climbing speed"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ladder"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixelSpeed())
@@ -109,14 +107,12 @@ 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())
@@ -124,7 +120,6 @@ PlatformerObjectBehavior::GetProperties(
behaviorContent.GetDoubleAttribute("slopeMaxAngle")));
properties["CanGrabPlatforms"]
.SetLabel(_("Can grab platform ledges"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ledge"))
.SetValue(behaviorContent.GetBoolAttribute("canGrabPlatforms", false)
? "true"
@@ -133,7 +128,6 @@ 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"
@@ -141,7 +135,6 @@ PlatformerObjectBehavior::GetProperties(
.SetType("Boolean");
properties["YGrabOffset"]
.SetLabel(_("Grab offset on Y axis"))
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
.SetGroup(_("Ledge"))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
@@ -149,7 +142,6 @@ 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())
@@ -166,7 +158,6 @@ 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

@@ -7,8 +7,7 @@ namespace gdjs {
* Returned by _findHighestFloorAndMoveOnTop
*/
type PlatformSearchResult = {
highestGroundPlatform: gdjs.PlatformRuntimeBehavior | null;
highestGroundPolygon: gdjs.Polygon | null;
highestGround: gdjs.PlatformRuntimeBehavior | null;
isCollidingAnyPlatform: boolean;
};
@@ -75,8 +74,7 @@ namespace gdjs {
* Returned by _findHighestFloorAndMoveOnTop
*/
private static readonly _platformSearchResult: PlatformSearchResult = {
highestGroundPlatform: null,
highestGroundPolygon: null,
highestGround: null,
isCollidingAnyPlatform: false,
};
@@ -614,8 +612,7 @@ namespace gdjs {
this._potentialCollidingObjects,
floorPlatformId,
/*excludeJumpthrus=*/
true,
this._onFloor.getFloorPolygon()
true
)
) {
if (
@@ -651,12 +648,12 @@ namespace gdjs {
// This is to be consistent on all floor collision.
// The object will land right on floor.
const { highestGroundPlatform } = this._findHighestFloorAndMoveOnTop(
const { highestGround } = this._findHighestFloorAndMoveOnTop(
this._potentialCollidingObjects,
0,
this._requestedDeltaY
);
if (!highestGroundPlatform) {
if (!highestGround) {
object.setY(object.getY() + this._requestedDeltaY);
}
} else {
@@ -709,13 +706,10 @@ namespace gdjs {
this._falling.enter(from);
}
_setOnFloor(
collidingPlatform: gdjs.PlatformRuntimeBehavior,
floorPolygon: gdjs.Polygon
) {
_setOnFloor(collidingPlatform: gdjs.PlatformRuntimeBehavior) {
this._state.leave();
this._state = this._onFloor;
this._onFloor.enter(collidingPlatform, floorPolygon);
this._onFloor.enter(collidingPlatform);
}
private _setJumping() {
@@ -809,28 +803,22 @@ namespace gdjs {
// The interval could be smaller.
// It's just for rounding errors.
const {
highestGroundPlatform,
highestGroundPolygon,
} = this._findHighestFloorAndMoveOnTop(
const { highestGround } = this._findHighestFloorAndMoveOnTop(
this._potentialCollidingObjects,
-1,
1
);
// don't fall if GrabbingPlatform or OnLadder
if (this._state === this._onFloor) {
if (!highestGroundPlatform || !highestGroundPolygon) {
if (!highestGround) {
this._setFalling();
} else if (
highestGroundPlatform === this._onFloor.getFloorPlatform() &&
highestGroundPolygon === this._onFloor.getFloorPolygon()
) {
} else if (highestGround === this._onFloor.getFloorPlatform()) {
this._onFloor.updateFloorPosition();
} else {
this._setOnFloor(highestGroundPlatform, highestGroundPolygon);
this._setOnFloor(highestGround);
}
} else if (highestGroundPlatform && highestGroundPolygon && canLand) {
this._setOnFloor(highestGroundPlatform, highestGroundPolygon);
} else if (highestGround && canLand) {
this._setOnFloor(highestGround);
} else {
// The object can't land.
object.setY(oldY);
@@ -942,15 +930,13 @@ namespace gdjs {
*/
_isCollidingWithOneOf(
candidates: gdjs.PlatformRuntimeBehavior[],
ignoredPlatformId?: number | null,
excludeJumpThrus?: boolean,
ignoredPolygon?: gdjs.Polygon | null
exceptThisOne?: number | null,
excludeJumpThrus?: boolean
) {
excludeJumpThrus = !!excludeJumpThrus;
for (let i = 0; i < candidates.length; ++i) {
const platform = candidates[i];
const isPlatformIgnored = platform.owner.id === ignoredPlatformId;
if (isPlatformIgnored && !ignoredPolygon) {
if (platform.owner.id === exceptThisOne) {
continue;
}
if (
@@ -966,10 +952,9 @@ namespace gdjs {
}
if (
gdjs.RuntimeObject.collisionTest(
platform.owner,
this.owner,
this._ignoreTouchingEdges,
isPlatformIgnored ? ignoredPolygon : null
platform.owner,
this._ignoreTouchingEdges
)
) {
return true;
@@ -997,8 +982,7 @@ namespace gdjs {
context.initializeBeforeSearch(this, upwardDeltaY, downwardDeltaY);
let totalHighestY = Number.MAX_VALUE;
let highestGroundPlatform: gdjs.PlatformRuntimeBehavior | null = null;
let highestGroundPolygon: gdjs.Polygon | null = null;
let highestGround: gdjs.PlatformRuntimeBehavior | null = null;
let isCollidingAnyPlatform = false;
for (const platform of candidates) {
if (
@@ -1048,8 +1032,7 @@ namespace gdjs {
// and is too high for the character to walk on.
// This will still be an obstacle even if there
// are other platforms that fit the requirements.
highestGroundPlatform = null;
highestGroundPolygon = null;
highestGround = null;
break;
}
@@ -1058,18 +1041,16 @@ namespace gdjs {
highestRelativeY < totalHighestY
) {
totalHighestY = highestRelativeY;
highestGroundPlatform = platform;
highestGroundPolygon = context.highestFloorPolygon;
highestGround = platform;
}
}
if (highestGroundPlatform) {
if (highestGround) {
const object = this.owner;
object.setY(object.getY() + totalHighestY);
}
const returnValue =
gdjs.PlatformerObjectRuntimeBehavior._platformSearchResult;
returnValue.highestGroundPlatform = highestGroundPlatform;
returnValue.highestGroundPolygon = highestGroundPolygon;
returnValue.highestGround = highestGround;
returnValue.isCollidingAnyPlatform = isCollidingAnyPlatform;
return returnValue;
}
@@ -1134,7 +1115,7 @@ namespace gdjs {
(vertex[0] === context.ownerMaxX &&
(previousVertex[0] < vertex[0] || nextVertex[0] < vertex[0]))
) {
context.addPointConstraint(vertex[1], hitbox);
context.addPointConstraint(vertex[1]);
}
const deltaX = vertex[0] - previousVertex[0];
@@ -1152,7 +1133,7 @@ namespace gdjs {
previousVertex[1] +
((context.ownerMinX - previousVertex[0]) * deltaY) / deltaX;
context.addPointConstraint(intersectionY, hitbox);
context.addPointConstraint(intersectionY);
}
// Check intersection on the right side of owner
if (
@@ -1166,7 +1147,7 @@ namespace gdjs {
previousVertex[1] +
((context.ownerMaxX - previousVertex[0]) * deltaY) / deltaX;
context.addPointConstraint(intersectionY, hitbox);
context.addPointConstraint(intersectionY);
}
}
if (context.floorIsTooHigh()) {
@@ -1886,7 +1867,6 @@ namespace gdjs {
class OnFloor implements State {
private _behavior: PlatformerObjectRuntimeBehavior;
private _floorPlatform: gdjs.PlatformRuntimeBehavior | null = null;
private _floorPolygon: gdjs.Polygon | null = null;
private _floorLastX: float = 0;
private _floorLastY: float = 0;
_oldHeight: float = 0;
@@ -1899,16 +1879,8 @@ namespace gdjs {
return this._floorPlatform;
}
getFloorPolygon() {
return this._floorPolygon;
}
enter(
floorPlatform: gdjs.PlatformRuntimeBehavior,
floorPolygon: gdjs.Polygon
) {
enter(floorPlatform: gdjs.PlatformRuntimeBehavior) {
this._floorPlatform = floorPlatform;
this._floorPolygon = floorPolygon;
this.updateFloorPosition();
this._behavior._canJump = true;
this._behavior._currentFallSpeed = 0;
@@ -1916,7 +1888,6 @@ namespace gdjs {
leave() {
this._floorPlatform = null;
this._floorPolygon = null;
}
updateFloorPosition() {
@@ -2029,23 +2000,17 @@ namespace gdjs {
behavior._requestedDeltaX * behavior._slopeClimbingFactor
);
const {
highestGroundPlatform,
highestGroundPolygon,
highestGround,
isCollidingAnyPlatform,
} = behavior._findHighestFloorAndMoveOnTop(
behavior._potentialCollidingObjects,
-deltaMaxY,
deltaMaxY
);
if (
highestGroundPlatform &&
highestGroundPolygon &&
(highestGroundPlatform !== this._floorPlatform ||
highestGroundPolygon !== this._floorPolygon)
) {
behavior._setOnFloor(highestGroundPlatform, highestGroundPolygon);
if (highestGround && highestGround !== this._floorPlatform) {
behavior._setOnFloor(highestGround);
}
if (highestGroundPlatform === null && isCollidingAnyPlatform) {
if (highestGround === null && isCollidingAnyPlatform) {
// Unable to follow the floor (too steep): go back to the original position.
behavior.owner.setX(oldX);
}
@@ -2055,7 +2020,7 @@ namespace gdjs {
// Try to follow the platform until the obstacle.
const {
highestGroundPlatform: highestGroundOnPlatform,
highestGround: highestGroundOnPlatform,
isCollidingAnyPlatform,
} = behavior._findHighestFloorAndMoveOnTop(
behavior._potentialCollidingObjects,
@@ -2090,7 +2055,7 @@ namespace gdjs {
// 1. Try to move 1 pixel on the X axis to climb the junction.
object.setX(object.getX() + Math.sign(requestedDeltaX));
const {
highestGroundPlatform: highestGroundAtJunction,
highestGround: highestGroundAtJunction,
} = behavior._findHighestFloorAndMoveOnTop(
behavior._potentialCollidingObjects,
// Look up from at least 1 pixel to bypass not perfectly aligned floors.
@@ -2110,27 +2075,23 @@ namespace gdjs {
);
object.setX(object.getX() + deltaX);
const {
highestGroundPlatform: highestGroundOnObstacle,
highestGroundPolygon,
highestGround: highestGroundOnObstacle,
} = behavior._findHighestFloorAndMoveOnTop(
behavior._potentialCollidingObjects,
// Do an exact slope angle check.
-Math.abs(deltaX) * behavior._slopeClimbingFactor,
0
);
if (highestGroundOnObstacle && highestGroundPolygon) {
if (highestGroundOnObstacle) {
// The obstacle slope can be climbed.
if (Math.abs(remainingDeltaX) >= 2) {
behavior._setOnFloor(
highestGroundOnObstacle,
highestGroundPolygon
);
behavior._setOnFloor(highestGroundOnObstacle);
} else {
// We went too far in order to check that.
// Now, find the right position on the obstacles.
object.setPosition(oldX + requestedDeltaX, beforeObstacleY);
const {
highestGroundPlatform: highestGroundOnObstacle,
highestGround: highestGroundOnObstacle,
} = behavior._findHighestFloorAndMoveOnTop(
behavior._potentialCollidingObjects,
// requestedDeltaX can be small when the object start moving.
@@ -2142,11 +2103,8 @@ namespace gdjs {
0
);
// Should always be true
if (highestGroundOnObstacle && highestGroundPolygon) {
behavior._setOnFloor(
highestGroundOnObstacle,
highestGroundPolygon
);
if (highestGroundOnObstacle) {
behavior._setOnFloor(highestGroundOnObstacle);
}
}
} else {
@@ -2620,8 +2578,6 @@ namespace gdjs {
*/
foundUnderBottom: boolean = false;
highestFloorPolygon: gdjs.Polygon | null = null;
initializeBeforeSearch(
behavior: PlatformerObjectRuntimeBehavior,
upwardDeltaY: float,
@@ -2697,7 +2653,7 @@ namespace gdjs {
* and update the context with this new constraint.
* @param y
*/
addPointConstraint(y: float, sourcePolygon: gdjs.Polygon): void {
addPointConstraint(y: float): void {
if (y < this.floorMinY) {
// The platform is too high to walk on...
if (y > this.headMaxY) {
@@ -2737,7 +2693,6 @@ namespace gdjs {
this.allowedMaxDeltaY,
y - this.ownerMaxY
);
this.highestFloorPolygon = sourcePolygon;
}
}
}

View File

@@ -1644,6 +1644,8 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
behavior = object.getBehavior('auto1');
object.setCustomWidthAndHeight(10, 20);
runtimeScene.addObject(object);
// The object is in the corner of the platform.
object.setPosition(80 - 10, 80 - 20);
});
[
@@ -1670,9 +1672,6 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
it(`can jump while moving against a wall ${wallBeing}`, function () {
createPlatforms(runtimeScene);
// The object is in the corner of the platform.
object.setPosition(80 - 10, 80 - 20);
// The object stays on the platform.
for (let i = 0; i < 5; ++i) {
runtimeScene.renderAndStep(1000 / 60);
@@ -1697,32 +1696,6 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
expect(object.getY()).to.be.lessThan(oldY);
}
});
it(`can move against a wall ${wallBeing}`, function () {
createPlatforms(runtimeScene);
// The object is 20 pixels from the wall.
object.setPosition(80 - 10 - 10, 80 - 20);
// The object stays on the platform.
for (let i = 0; i < 5; ++i) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(object.getY()).to.within(60 - epsilon, 60 + epsilon);
expect(behavior.isFalling()).to.be(false);
expect(behavior.isFallingWithoutJumping()).to.be(false);
expect(behavior.isMoving()).to.be(false);
// It's important the character moves from several pixels per frame.
behavior.setCurrentSpeed(behavior.getMaxSpeed());
for (let i = 0; i < 10; ++i) {
const oldY = object.getY();
behavior.simulateRightKey();
runtimeScene.renderAndStep(1000 / 60);
}
// The object is against the wall.
expect(object.getX()).to.be(80 - 10);
});
});
});

View File

@@ -86,12 +86,6 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
return platform;
};
/**
* @param {gdjs.PlatformerObjectRuntimeBehavior} characterBehavior
* @param {gdjs.PlatformRuntimeBehavior} platformBehavior
* @param {float} upwardDeltaY
* @param {float} downwardDeltaY
*/
const checkMoveOn = (
characterBehavior,
platformBehavior,
@@ -103,15 +97,9 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
upwardDeltaY,
downwardDeltaY
);
expect(result.highestGroundPlatform).to.be(platformBehavior);
expect(result.highestGround).to.be(platformBehavior);
};
/**
* @param {gdjs.PlatformerObjectRuntimeBehavior} characterBehavior
* @param {gdjs.PlatformRuntimeBehavior} platformBehavior
* @param {float} upwardDeltaY
* @param {float} downwardDeltaY
*/
const checkNoFloor = (
characterBehavior,
platformBehavior,
@@ -124,17 +112,11 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
upwardDeltaY,
downwardDeltaY
);
expect(result.highestGroundPlatform).to.be(null);
expect(result.highestGround).to.be(null);
expect(result.isCollidingAnyPlatform).to.be(false);
expect(characterBehavior.owner.getY()).to.be(oldY);
};
/**
* @param {gdjs.PlatformerObjectRuntimeBehavior} characterBehavior
* @param {gdjs.PlatformRuntimeBehavior} platformBehavior
* @param {float} upwardDeltaY
* @param {float} downwardDeltaY
*/
const checkObstacle = (
characterBehavior,
platformBehavior,
@@ -147,7 +129,7 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
upwardDeltaY,
downwardDeltaY
);
expect(result.highestGroundPlatform).to.be(null);
expect(result.highestGround).to.be(null);
expect(result.isCollidingAnyPlatform).to.be(true);
expect(characterBehavior.owner.getY()).to.be(oldY);
};
@@ -311,13 +293,11 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
describe(description, function () {
const runtimeScene = makeTestRuntimeScene();
const character = addCharacter(runtimeScene);
/** @type gdjs.PlatformerObjectRuntimeBehavior */
const behavior = character.getBehavior('auto1');
const platform = addPlatform(runtimeScene, mask);
platform.setCustomWidthAndHeight(300, 300);
platform.setPosition(position[0], position[1]);
/** @type gdjs.PlatformRuntimeBehavior */
const platformBehavior = platform.getBehavior('Platform');
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
runtimeScene
@@ -385,13 +365,11 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
describe(description, function () {
const runtimeScene = makeTestRuntimeScene();
const character = addCharacter(runtimeScene);
/** @type gdjs.PlatformerObjectRuntimeBehavior */
const behavior = character.getBehavior('auto1');
const platform = addPlatform(runtimeScene, mask);
platform.setCustomWidthAndHeight(300, 300);
platform.setPosition(position[0], position[1]);
/** @type gdjs.PlatformRuntimeBehavior */
const platformBehavior = platform.getBehavior('Platform');
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
runtimeScene

View File

@@ -73,38 +73,6 @@ 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

@@ -116,18 +116,6 @@ void SpineObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker &work
worker.ExposeEmbeddeds(spineResourceName);
}
const gd::String &
SpineObjectConfiguration::GetAnimationName(size_t index) const {
return GetAnimation(index).GetName();
}
bool SpineObjectConfiguration::HasAnimationNamed(const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const SpineAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
const SpineAnimation &
SpineObjectConfiguration::GetAnimation(std::size_t nb) const {
if (nb >= animations.size()) return badAnimation;
@@ -141,6 +129,13 @@ SpineAnimation &SpineObjectConfiguration::GetAnimation(std::size_t nb) {
return animations[nb];
}
bool SpineObjectConfiguration::HasAnimationNamed(const gd::String &name) const {
return !name.empty() && (find_if(animations.begin(), animations.end(),
[&name](const SpineAnimation &animation) {
return animation.GetName() == name;
}) != animations.end());
}
void SpineObjectConfiguration::AddAnimation(const SpineAnimation &animation) {
animations.push_back(animation);
}

View File

@@ -93,11 +93,15 @@ public:
*/
SpineAnimation &GetAnimation(std::size_t nb);
std::size_t GetAnimationsCount() const override { return animations.size(); };
/**
* \brief Return the number of animations this object has.
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
const gd::String &GetAnimationName(size_t index) const override;
bool HasAnimationNamed(const gd::String &animationName) const override;
/**
* \brief Return true if the animation called "name" exists.
*/
bool HasAnimationNamed(const gd::String& name) const;
/**
* \brief Add an animation at the end of the existing ones.

View File

@@ -2,21 +2,6 @@ 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;
@@ -197,44 +182,6 @@ 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();

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