diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index b732d71ef..0ba5cbb4d 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -1032,6 +1032,8 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( behaviorType, oldPropertyName, newPropertyName); projectBrowser.ExposeObjects(project, behaviorPropertyRenamer); + // TODO Rename the property in behavior overridings of InitialInstances. + if (properties.Get(oldPropertyName).GetType() == "Behavior") { // This is a property representing another behavior that must exist on the // object. diff --git a/Core/GDCore/Project/BehaviorConfigurationContainer.cpp b/Core/GDCore/Project/BehaviorConfigurationContainer.cpp index 9dea002cc..4afbf5cde 100644 --- a/Core/GDCore/Project/BehaviorConfigurationContainer.cpp +++ b/Core/GDCore/Project/BehaviorConfigurationContainer.cpp @@ -14,8 +14,8 @@ BehaviorConfigurationContainer::~BehaviorConfigurationContainer(){}; std::map BehaviorConfigurationContainer::GetProperties() const { - return GetProperties(content); - }; + return GetProperties(content); +} std::map BehaviorConfigurationContainer::GetProperties( const gd::SerializerElement& behaviorContent) const { diff --git a/Core/GDCore/Project/BehaviorConfigurationContainer.h b/Core/GDCore/Project/BehaviorConfigurationContainer.h index a3e70d93e..8d43dd12f 100644 --- a/Core/GDCore/Project/BehaviorConfigurationContainer.h +++ b/Core/GDCore/Project/BehaviorConfigurationContainer.h @@ -81,7 +81,6 @@ class GD_CORE_API BehaviorConfigurationContainer { * behavior * * \return false if the new value cannot be set - * \see gd::InitialInstance */ bool UpdateProperty(const gd::String& name, const gd::String& value) { return UpdateProperty(content, name, value); @@ -98,11 +97,42 @@ class GD_CORE_API BehaviorConfigurationContainer { return RenameProperty(content, oldName, newName); }; + /** + * \brief Called when the IDE wants to remove a custom property from the + * behavior + * + * \see gd::InitialInstance + */ + void RemoveProperty(const gd::String &name) { + content.RemoveChild(name); + content.RemoveAttribute(name); + }; + + /** + * \brief Called to check if a property is overriden. + * + * \see gd::InitialInstance + */ + bool HasPropertyValue(const gd::String name) const { + return content.HasChild(name) || content.HasAttribute(name); + } + /** * \brief Called to initialize the content with the default properties * for the behavior. */ - virtual void InitializeContent() { InitializeContent(content); }; + virtual void InitializeContent() { + InitializeContent(content); + }; + + /** + * \brief Called to remove all properties values for the behavior. + * + * \see gd::InitialInstance + */ + void ClearContent() { + content.Clear(); + }; /** * \brief Serialize the behavior content. @@ -202,7 +232,6 @@ class GD_CORE_API BehaviorConfigurationContainer { * behavior * * \return false if the new value cannot be set - * \see gd::InitialInstance */ virtual bool UpdateProperty(gd::SerializerElement& behaviorContent, const gd::String& name, diff --git a/Core/GDCore/Project/BehaviorsContainer.cpp b/Core/GDCore/Project/BehaviorsContainer.cpp new file mode 100644 index 000000000..a9e70b1e1 --- /dev/null +++ b/Core/GDCore/Project/BehaviorsContainer.cpp @@ -0,0 +1,205 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "GDCore/Project/BehaviorsContainer.h" + +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Project/Behavior.h" +#include "GDCore/Project/CustomBehavior.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Project/PropertyDescriptor.h" +#include "GDCore/Project/QuickCustomization.h" +#include "GDCore/Serialization/SerializerElement.h" +#include "GDCore/Tools/Log.h" +#include "GDCore/Tools/UUID/UUID.h" + +namespace gd { + +BehaviorsContainer::~BehaviorsContainer() {} + +BehaviorsContainer::BehaviorsContainer(bool isOverriding_) + : isOverriding(isOverriding_) {} + +void BehaviorsContainer::Init(const gd::BehaviorsContainer &behaviorsContainer) { + isOverriding = behaviorsContainer.isOverriding; + behaviors = gd::Clone(behaviorsContainer.behaviors); +} + +std::vector BehaviorsContainer::GetAllBehaviorNames() const { + std::vector allNameIdentifiers; + + for (auto &it : behaviors) + allNameIdentifiers.push_back(it.first); + + return allNameIdentifiers; +} + +void BehaviorsContainer::RemoveBehavior(const gd::String &name) { + behaviors.erase(name); +} + +bool BehaviorsContainer::RenameBehavior(const gd::String &name, + const gd::String &newName) { + if (behaviors.find(name) == behaviors.end() || + behaviors.find(newName) != behaviors.end()) + return false; + + std::unique_ptr aut = std::move(behaviors.find(name)->second); + behaviors.erase(name); + behaviors[newName] = std::move(aut); + behaviors[newName]->SetName(newName); + + return true; +} + +gd::Behavior &BehaviorsContainer::GetBehavior(const gd::String &name) { + return *behaviors.find(name)->second; +} + +const gd::Behavior & +BehaviorsContainer::GetBehavior(const gd::String &name) const { + return *behaviors.find(name)->second; +} + +bool BehaviorsContainer::HasBehaviorNamed(const gd::String &name) const { + return behaviors.find(name) != behaviors.end(); +} + +gd::Behavior *BehaviorsContainer::AddNewBehavior(const gd::Project &project, + const gd::String &type, + const gd::String &name) { + auto initializeAndAdd = [this, + &name](std::unique_ptr behavior) { + if (isOverriding) { + // We don't call the Initialize method for behavior overridings because they + // should have no property value initially. + behavior->ClearContent(); + } + else { + behavior->InitializeContent(); + } + this->behaviors[name] = std::move(behavior); + return this->behaviors[name].get(); + }; + + if (project.HasEventsBasedBehavior(type)) { + return initializeAndAdd( + gd::make_unique(name, project, type)); + } else { + const gd::BehaviorMetadata &behaviorMetadata = + gd::MetadataProvider::GetBehaviorMetadata(project.GetCurrentPlatform(), + type); + if (gd::MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) { + gd::LogWarning("Tried to create a behavior with an unknown type: " + + type); + // It's probably an events-based behavior that was removed. + // Create a custom behavior to preserve the properties values. + return initializeAndAdd( + gd::make_unique(name, project, type)); + } + std::unique_ptr behavior(behaviorMetadata.Get().Clone()); + behavior->SetName(name); + return initializeAndAdd(std::move(behavior)); + } +} + +void BehaviorsContainer::UnserializeFrom(gd::Project &project, + const SerializerElement &element) { + element.ConsiderAsArrayOf("behavior", "automatism"); + for (std::size_t i = 0; i < element.GetChildrenCount(); ++i) { + SerializerElement &behaviorElement = element.GetChild(i); + + gd::String type = behaviorElement.GetStringAttribute("type").FindAndReplace( + "Automatism", "Behavior"); // Compatibility with GD <= 4 + gd::String name = behaviorElement.GetStringAttribute("name"); + + auto behavior = gd::BehaviorsContainer::AddNewBehavior(project, type, name); + // Compatibility with GD <= 4.0.98 + // If there is only one child called "content" (in addition to "type" and + // "name"), it's the content of a JavaScript behavior. Move the content + // out of the "content" object (to put it directly at the root of the + // behavior element). + if (behaviorElement.HasChild("content") && + behaviorElement.GetAllChildren().size() == 3) { + SerializerElement &contentElement = behaviorElement.GetChild("content"); + + // Physics2 Behavior was using "type" for the type of the body. The name + // conflicts with the behavior "type". Rename it. + if (contentElement.HasChild("type")) { + contentElement.AddChild("bodyType") + .SetValue(contentElement.GetChild("type").GetStringValue()); + contentElement.RemoveChild("type"); + } + + behavior->UnserializeFrom(contentElement); + } + // end of compatibility code + else { + behavior->UnserializeFrom(behaviorElement); + } + + bool isFolded = behaviorElement.GetBoolAttribute("isFolded", false); + behavior->SetFolded(isFolded); + + // Handle Quick Customization info. + if (behaviorElement.HasChild("propertiesQuickCustomizationVisibilities")) { + behavior->GetPropertiesQuickCustomizationVisibilities().UnserializeFrom( + behaviorElement.GetChild("propertiesQuickCustomizationVisibilities")); + } + if (behaviorElement.HasChild("quickCustomizationVisibility")) { + behavior->SetQuickCustomizationVisibility( + QuickCustomization::StringAsVisibility( + behaviorElement.GetStringAttribute( + "quickCustomizationVisibility"))); + } + } +} + +void BehaviorsContainer::SerializeTo(SerializerElement &element) const { + element.ConsiderAsArrayOf("behavior"); + std::vector allBehaviors = GetAllBehaviorNames(); + for (std::size_t i = 0; i < allBehaviors.size(); ++i) { + const gd::Behavior &behavior = GetBehavior(allBehaviors[i]); + // Default behaviors are added at the object creation according to + // metadata. They don't need to be serialized. + // During the export, all behaviors are set as not default by + // `BehaviorDefaultFlagClearer` because the Runtime needs all the behaviors. + if (behavior.IsDefaultBehavior()) { + continue; + } + SerializerElement &behaviorElement = element.AddChild("behavior"); + + behavior.SerializeTo(behaviorElement); + behaviorElement.RemoveChild("type"); // The content can contain type or + // name properties, remove them. + behaviorElement.RemoveChild("name"); + behaviorElement.RemoveChild("isFolded"); + behaviorElement.SetAttribute("type", behavior.GetTypeName()); + behaviorElement.SetAttribute("name", behavior.GetName()); + if (behavior.IsFolded()) + behaviorElement.SetAttribute("isFolded", true); + + // Handle Quick Customization info. + behaviorElement.RemoveChild("propertiesQuickCustomizationVisibilities"); + const QuickCustomizationVisibilitiesContainer + &propertiesQuickCustomizationVisibilities = + behavior.GetPropertiesQuickCustomizationVisibilities(); + if (!propertiesQuickCustomizationVisibilities.IsEmpty()) { + propertiesQuickCustomizationVisibilities.SerializeTo( + behaviorElement.AddChild("propertiesQuickCustomizationVisibilities")); + } + const QuickCustomization::Visibility visibility = + behavior.GetQuickCustomizationVisibility(); + if (visibility != QuickCustomization::Visibility::Default) { + behaviorElement.SetAttribute( + "quickCustomizationVisibility", + QuickCustomization::VisibilityAsString(visibility)); + } + } +} + +} // namespace gd diff --git a/Core/GDCore/Project/BehaviorsContainer.h b/Core/GDCore/Project/BehaviorsContainer.h new file mode 100644 index 000000000..4954ab475 --- /dev/null +++ b/Core/GDCore/Project/BehaviorsContainer.h @@ -0,0 +1,158 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#pragma once + +#include +#include +#include + +#include "GDCore/Project/Behavior.h" +#include "GDCore/Project/EffectsContainer.h" +#include "GDCore/Project/ObjectConfiguration.h" +#include "GDCore/Project/VariablesContainer.h" +#include "GDCore/String.h" +#include "GDCore/Tools/MakeUnique.h" +#include "GDCore/Vector2.h" + +namespace gd { +class PropertyDescriptor; +class Project; +class Layout; +class ArbitraryResourceWorker; +class InitialInstance; +class SerializerElement; +class EffectsContainer; +} // namespace gd + +namespace gd { + +/** + * \brief Represent an behaviors container of a platform + * + * \ingroup PlatformDefinition + */ +class GD_CORE_API BehaviorsContainer { +public: + /** + * Create a new behaviors container with the name passed as argument. + */ + BehaviorsContainer(bool isOverriding = false); + + /** + * Copy constructor. Calls Init(). + */ + BehaviorsContainer(const gd::BehaviorsContainer &behaviorsContainer) { + Init(behaviorsContainer); + }; + + /** + * Assignment operator. Calls Init(). + */ + BehaviorsContainer & + operator=(const gd::BehaviorsContainer &behaviorsContainer) { + if ((this) != &behaviorsContainer) + Init(behaviorsContainer); + return *this; + } + + /** + * Destructor. + */ + virtual ~BehaviorsContainer(); + + /** \name Behaviors management + * Members functions related to behaviors management. + */ + ///@{ + + /** + * \brief Return a vector containing the names of all the behaviors used by + * the behaviors container + */ + std::vector GetAllBehaviorNames() const; + + /** + * \brief Return a reference to the content of the behavior called \a name. + */ + Behavior &GetBehavior(const gd::String &name); + + /** + * \brief Return a reference to the content of the behavior called \a name. + */ + const Behavior &GetBehavior(const gd::String &name) const; + + /** + * \brief Return true if behaviors container has a behavior called \a name. + */ + bool HasBehaviorNamed(const gd::String &name) const; + + /** + * \brief Remove behavior called \a name + */ + void RemoveBehavior(const gd::String &name); + + /** + * \brief Change the name of behavior called name to newName. + * \return true if name was successfully changed + */ + bool RenameBehavior(const gd::String &name, const gd::String &newName); + + /** + * \brief Add the behavior of the specified \a type with the specified \a + * name. + * + * The project's current platform is used to initialize the content. + * + * \return A pointer to the newly added behavior content. NULL if the creation + * failed. + */ + gd::Behavior *AddNewBehavior(const gd::Project &project, + const gd::String &type, const gd::String &name); + + /** + * \brief Get a read-only access to the map containing the behaviors with + * their properties. + */ + const std::map> & + GetAllBehaviorContents() const { + return behaviors; + }; + ///@} + + /** \name Serialization + * Members functions related to serialization of the behaviors container + */ + ///@{ + /** + * \brief Serialize the behaviors container. + * \see DoSerializeTo + */ + void SerializeTo(SerializerElement &element) const; + + /** + * \brief Unserialize the behaviors container. + * \see DoUnserializeFrom + */ + void UnserializeFrom(gd::Project &project, const SerializerElement &element); + +protected: + bool isOverriding = false; + std::map> + behaviors; ///< Contains all behaviors and their properties for the + ///< behaviors container. Behavior contents are the ownership + ///< of the behaviors container. + + /** + * Initialize behaviors container using another behaviors container. Used by + * copy-ctor and assign-op. Don't forget to update me if members were changed! + * + * It's needed because there is no default copy for a map of unique_ptr like + * behaviors and it must be a deep copy. + */ + void Init(const gd::BehaviorsContainer &behaviorsContainer); +}; + +} // namespace gd diff --git a/Core/GDCore/Project/EventsBasedObjectVariant.cpp b/Core/GDCore/Project/EventsBasedObjectVariant.cpp index a917912c3..2f3ba0e03 100644 --- a/Core/GDCore/Project/EventsBasedObjectVariant.cpp +++ b/Core/GDCore/Project/EventsBasedObjectVariant.cpp @@ -66,7 +66,7 @@ void EventsBasedObjectVariant::UnserializeFrom( } else { layers.Reset(); } - initialInstances.UnserializeFrom(element.GetChild("instances")); + initialInstances.UnserializeFrom(project, element.GetChild("instances")); editorSettings.UnserializeFrom(element.GetChild("editionSettings")); } diff --git a/Core/GDCore/Project/ExternalLayout.cpp b/Core/GDCore/Project/ExternalLayout.cpp index a7a15781f..2da46ae3e 100644 --- a/Core/GDCore/Project/ExternalLayout.cpp +++ b/Core/GDCore/Project/ExternalLayout.cpp @@ -12,9 +12,10 @@ namespace gd { -void ExternalLayout::UnserializeFrom(const SerializerElement& element) { +void ExternalLayout::UnserializeFrom(gd::Project &project, + const SerializerElement &element) { name = element.GetStringAttribute("name", "", "Name"); - instances.UnserializeFrom(element.GetChild("instances", 0, "Instances")); + instances.UnserializeFrom(project, element.GetChild("instances", 0, "Instances")); editorSettings.UnserializeFrom(element.GetChild("editionSettings")); associatedLayout = element.GetStringAttribute("associatedLayout"); } diff --git a/Core/GDCore/Project/ExternalLayout.h b/Core/GDCore/Project/ExternalLayout.h index 6c25db8f2..51b4a5752 100644 --- a/Core/GDCore/Project/ExternalLayout.h +++ b/Core/GDCore/Project/ExternalLayout.h @@ -4,8 +4,8 @@ * reserved. This project is released under the MIT License. */ -#ifndef GDCORE_EXTERNALLAYOUT_H -#define GDCORE_EXTERNALLAYOUT_H +#pragma once + #include #include "GDCore/Project/InitialInstancesContainer.h" @@ -86,7 +86,7 @@ class GD_CORE_API ExternalLayout { /** * \brief Unserialize the external layout. */ - void UnserializeFrom(const SerializerElement& element); + void UnserializeFrom(gd::Project &project, const SerializerElement& element); ///@} private: @@ -98,4 +98,3 @@ class GD_CORE_API ExternalLayout { } // namespace gd -#endif // GDCORE_EXTERNALLAYOUT_H diff --git a/Core/GDCore/Project/InitialInstance.cpp b/Core/GDCore/Project/InitialInstance.cpp index 3944bcc9e..589120a02 100644 --- a/Core/GDCore/Project/InitialInstance.cpp +++ b/Core/GDCore/Project/InitialInstance.cpp @@ -13,6 +13,10 @@ #include "GDCore/Project/PropertyDescriptor.h" #include "GDCore/Serialization/SerializerElement.h" #include "GDCore/Tools/UUID/UUID.h" +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Project/CustomBehavior.h" +#include "GDCore/Tools/Log.h" namespace gd { @@ -20,29 +24,12 @@ gd::String* InitialInstance::badStringPropertyValue = NULL; InitialInstance::InitialInstance() : objectName(""), - x(0), - y(0), - z(0), - angle(0), - rotationX(0), - rotationY(0), - zOrder(0), - opacity(255), layer(""), - flippedX(false), - flippedY(false), - flippedZ(false), - customSize(false), - customDepth(false), - width(0), - height(0), - depth(0), - locked(false), - sealed(false), - keepRatio(true), - persistentUuid(UUID::MakeUuid4()) {} + persistentUuid(UUID::MakeUuid4()), + behaviorOverridings(true) {} -void InitialInstance::UnserializeFrom(const SerializerElement& element) { +void InitialInstance::UnserializeFrom(gd::Project &project, + const SerializerElement &element) { SetObjectName(element.GetStringAttribute("name", "", "nom")); SetX(element.GetDoubleAttribute("x")); SetY(element.GetDoubleAttribute("y")); @@ -131,6 +118,11 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) { GetVariables().UnserializeFrom( element.GetChild("initialVariables", 0, "InitialVariables")); } + + if (element.HasChild("behaviorOverridings")) { + behaviorOverridings.UnserializeFrom( + project, element.GetChild("behaviorOverridings")); + } } void InitialInstance::SerializeTo(SerializerElement& element) const { @@ -178,6 +170,10 @@ void InitialInstance::SerializeTo(SerializerElement& element) const { } GetVariables().SerializeTo(element.AddChild("initialVariables")); + + if (!behaviorOverridings.GetAllBehaviorContents().empty()) { + behaviorOverridings.SerializeTo(element.AddChild("behaviorOverridings")); + } } InitialInstance& InitialInstance::ResetPersistentUuid() { @@ -243,4 +239,70 @@ void InitialInstance::SetRawStringProperty(const gd::String& name, stringProperties[name] = value; } +bool InitialInstance::HasAnyOverriddenProperty(const gd::Object &object) { + for (auto &behaviorOverridingPair : behaviorOverridings.GetAllBehaviorContents()) { + auto &behaviorName = behaviorOverridingPair.first; + if (HasAnyOverriddenPropertyForBehavior(object.GetBehavior(behaviorName))) { + return true; + } + } + return false; +} + +bool InitialInstance::HasAnyOverriddenPropertyForBehavior( + const gd::Behavior &behavior) { + auto &behaviorName = behavior.GetName(); + if (!HasBehaviorOverridingNamed(behaviorName)) { + return false; + } + auto &behaviorOverriding = GetBehaviorOverriding(behaviorName); + if (behaviorOverriding.GetContent().IsEmpty()) { + return false; + } + const auto &overridingProperties = behaviorOverriding.GetProperties(); + for (auto &propertyPair : behavior.GetProperties()) { + auto &propertyName = propertyPair.first; + auto &behaviorProperty = propertyPair.second; + + if (behaviorProperty.GetType() != "Behavior" && + behaviorOverriding.HasPropertyValue(propertyName) && + overridingProperties.find(propertyName) != + overridingProperties.end() && + overridingProperties.at(propertyName).GetValue() != + behaviorProperty.GetValue()) { + return true; + } + } + return false; +} + +gd::Behavior &InitialInstance::GetBehaviorOverriding(const gd::String &name) { + return behaviorOverridings.GetBehavior(name); +} + +const gd::Behavior & +InitialInstance::GetBehaviorOverriding(const gd::String &name) const { + return behaviorOverridings.GetBehavior(name); +} + +bool InitialInstance::HasBehaviorOverridingNamed(const gd::String &name) const { + return behaviorOverridings.HasBehaviorNamed(name); +} + +void InitialInstance::RemoveBehaviorOverriding(const gd::String &name) { + behaviorOverridings.RemoveBehavior(name); +} + +bool InitialInstance::RenameBehaviorOverriding(const gd::String &name, + const gd::String &newName) { + return behaviorOverridings.RenameBehavior(name, newName); +} + +gd::Behavior * +InitialInstance::AddNewBehaviorOverriding(const gd::Project &project, + const gd::String &type, + const gd::String &name) { + return behaviorOverridings.AddNewBehavior(project, type, name); +} + } // namespace gd diff --git a/Core/GDCore/Project/InitialInstance.h b/Core/GDCore/Project/InitialInstance.h index 831feb063..a84983da8 100644 --- a/Core/GDCore/Project/InitialInstance.h +++ b/Core/GDCore/Project/InitialInstance.h @@ -9,12 +9,15 @@ #include #include "GDCore/Project/VariablesContainer.h" +#include "GDCore/Project/BehaviorsContainer.h" #include "GDCore/String.h" namespace gd { class PropertyDescriptor; class Project; class Layout; class ObjectsContainer; +class Object; +class Behavior; } // namespace gd namespace gd { @@ -286,6 +289,67 @@ class GD_CORE_API InitialInstance { gd::VariablesContainer& GetVariables() { return initialVariables; } ///@} + /** \name Behavior management + * Members functions related to behavior overridings management. + */ + ///@{ + + /** + * \brief Return `true` if any property from the overriding has a different + * value from any behavior. + */ + bool HasAnyOverriddenProperty(const gd::Object &object); + + /** + * \brief Return `true` if any property from the overriding has a different + * value from the given behavior. + */ + bool HasAnyOverriddenPropertyForBehavior(const gd::Behavior &behavior); + + /** + * \brief Return a reference to the content of the overriding of of the + * behavior called \a name. + */ + Behavior &GetBehaviorOverriding(const gd::String &name); + + /** + * \brief Return a reference to the content of the overriding of the behavior + * called \a name. + */ + const Behavior &GetBehaviorOverriding(const gd::String &name) const; + + /** + * \brief Return true if the object instance overrides the object behavior + * called \a name. + */ + bool HasBehaviorOverridingNamed(const gd::String &name) const; + + /** + * \brief Remove the behavior overrides for the behavior called \a name + */ + void RemoveBehaviorOverriding(const gd::String &name); + + /** + * \brief Change the name of behavior called name to newName. + * \return true if name was successfully changed + */ + bool RenameBehaviorOverriding(const gd::String &name, + const gd::String &newName); + + /** + * \brief Add the behavior of the specified \a type with the specified \a + * name. + * + * The project's current platform is used to initialize the content. + * + * \return A pointer to the newly added behavior content. NULL if the creation + * failed. + */ + gd::Behavior *AddNewBehaviorOverriding(const gd::Project &project, + const gd::String &type, + const gd::String &name); + ///@} + /** \name Others properties management * Members functions related to exposing others properties of the instance. * @@ -365,7 +429,8 @@ class GD_CORE_API InitialInstance { /** * \brief Unserialize the instances container. */ - virtual void UnserializeFrom(const SerializerElement& element); + virtual void UnserializeFrom(gd::Project &project, + const SerializerElement &element); /** * \brief Reset the persistent UUID used to recognize @@ -376,8 +441,8 @@ class GD_CORE_API InitialInstance { /** * \brief Reset the persistent UUID used to recognize * the same initial instance between serialization. - */ - const gd::String& GetPersistentUuid() const { return persistentUuid; } + */ + const gd::String& GetPersistentUuid() const { return persistentUuid; } ///@} private: @@ -388,32 +453,36 @@ class GD_CORE_API InitialInstance { std::map stringProperties; ///< More data which can be used by the object - gd::String objectName; ///< Object name - double x; ///< Instance X position - double y; ///< Instance Y position - double z; ///< Instance Z position (for a 3D object) - double angle; ///< Instance angle on Z axis - double rotationX; ///< Instance angle on X axis (for a 3D object) - double rotationY; ///< Instance angle on Y axis (for a 3D object) - int zOrder; ///< Instance Z order (for a 2D object) - int opacity; ///< Instance opacity - bool flippedX; ///< True if the instance is flipped on X axis - bool flippedY; ///< True if the instance is flipped on Y axis - bool flippedZ; ///< True if the instance is flipped on Z axis - gd::String layer; ///< Instance layer - bool customSize; ///< True if object has a custom width and height - bool customDepth; ///< True if object has a custom depth - double width; ///< Instance custom width - double height; ///< Instance custom height - double depth; ///< Instance custom depth + gd::String objectName; ///< Object name + double x = 0; ///< Instance X position + double y = 0; ///< Instance Y position + double z = 0; ///< Instance Z position (for a 3D object) + double angle = 0; ///< Instance angle on Z axis + double rotationX = 0; ///< Instance angle on X axis (for a 3D object) + double rotationY = 0; ///< Instance angle on Y axis (for a 3D object) + int zOrder = 0; ///< Instance Z order (for a 2D object) + int opacity = 255; ///< Instance opacity + bool flippedX = false; ///< True if the instance is flipped on X axis + bool flippedY = false; ///< True if the instance is flipped on Y axis + bool flippedZ = false; ///< True if the instance is flipped on Z axis + gd::String layer; ///< Instance layer + bool customSize = false; ///< True if object has a custom width and height + bool customDepth = false; ///< True if object has a custom depth + double width = 0; ///< Instance custom width + double height = 0; ///< Instance custom height + double depth = 0; ///< Instance custom depth double defaultWidth = 0; ///< Instance default width as reported by InGameEditor double defaultHeight = 0; ///< Instance default height as reported by InGameEditor double defaultDepth = 0; ///< Instance default depth as reported by InGameEditor gd::VariablesContainer initialVariables; ///< Instance specific variables - bool locked; ///< True if the instance is locked - bool sealed; ///< True if the instance is sealed - bool keepRatio; ///< True if the instance's dimensions - /// should keep the same ratio. + gd::BehaviorsContainer + behaviorOverridings; ///< Contains all behavior property overriding for + ///< the instance. Behavior contents are the + ///< ownership of the instance. + bool locked = false; ///< True if the instance is locked + bool sealed = false; ///< True if the instance is sealed + bool keepRatio = true; ///< True if the instance's dimensions + /// should keep the same ratio. mutable gd::String persistentUuid; ///< A persistent random version 4 UUID, /// useful for hot reloading. diff --git a/Core/GDCore/Project/InitialInstancesContainer.cpp b/Core/GDCore/Project/InitialInstancesContainer.cpp index e7cc5b0ed..e9c838a8b 100644 --- a/Core/GDCore/Project/InitialInstancesContainer.cpp +++ b/Core/GDCore/Project/InitialInstancesContainer.cpp @@ -25,13 +25,13 @@ std::size_t InitialInstancesContainer::GetInstancesCount() const { } void InitialInstancesContainer::UnserializeFrom( - const SerializerElement& element) { + gd::Project &project, const SerializerElement &element) { initialInstances.clear(); element.ConsiderAsArrayOf("instance", "Objet"); for (std::size_t i = 0; i < element.GetChildrenCount(); ++i) { gd::InitialInstance instance; - instance.UnserializeFrom(element.GetChild(i)); + instance.UnserializeFrom(project, element.GetChild(i)); initialInstances.push_back(instance); } } diff --git a/Core/GDCore/Project/InitialInstancesContainer.h b/Core/GDCore/Project/InitialInstancesContainer.h index 2bb3c3a4c..163ed512a 100644 --- a/Core/GDCore/Project/InitialInstancesContainer.h +++ b/Core/GDCore/Project/InitialInstancesContainer.h @@ -187,10 +187,11 @@ class GD_CORE_API InitialInstancesContainer { /** * \brief Unserialize the instances container. */ - virtual void UnserializeFrom(const SerializerElement &element); + virtual void UnserializeFrom(gd::Project &project, + const SerializerElement &element); ///@} - private: +private: void RemoveInstanceIf( std::function predicate); diff --git a/Core/GDCore/Project/Layout.cpp b/Core/GDCore/Project/Layout.cpp index 42bbd1da9..d631e411f 100644 --- a/Core/GDCore/Project/Layout.cpp +++ b/Core/GDCore/Project/Layout.cpp @@ -334,7 +334,7 @@ void Layout::UnserializeFrom(gd::Project& project, objectsContainer.AddMissingObjectsInRootFolder(); initialInstances.UnserializeFrom( - element.GetChild("instances", 0, "Positions")); + project, element.GetChild("instances", 0, "Positions")); variables.UnserializeFrom(element.GetChild("variables", 0, "Variables")); layers.UnserializeLayersFrom(element.GetChild("layers", 0, "Layers")); diff --git a/Core/GDCore/Project/Object.cpp b/Core/GDCore/Project/Object.cpp index a2d660f9f..493b617b2 100644 --- a/Core/GDCore/Project/Object.cpp +++ b/Core/GDCore/Project/Object.cpp @@ -51,7 +51,7 @@ void Object::CopyWithoutConfiguration(const gd::Object& object) { assetStoreId = object.assetStoreId; objectVariables = object.objectVariables; effectsContainer = object.effectsContainer; - behaviors = gd::Clone(object.behaviors); + behaviors = object.behaviors; } gd::ObjectConfiguration& Object::GetConfiguration() { return *configuration; } @@ -61,69 +61,31 @@ const gd::ObjectConfiguration& Object::GetConfiguration() const { } std::vector Object::GetAllBehaviorNames() const { - std::vector allNameIdentifiers; - - for (auto& it : behaviors) allNameIdentifiers.push_back(it.first); - - return allNameIdentifiers; + return behaviors.GetAllBehaviorNames(); } -void Object::RemoveBehavior(const gd::String& name) { behaviors.erase(name); } +void Object::RemoveBehavior(const gd::String& name) { behaviors.RemoveBehavior(name); } bool Object::RenameBehavior(const gd::String& name, const gd::String& newName) { - if (behaviors.find(name) == behaviors.end() || - behaviors.find(newName) != behaviors.end()) - return false; - - std::unique_ptr aut = std::move(behaviors.find(name)->second); - behaviors.erase(name); - behaviors[newName] = std::move(aut); - behaviors[newName]->SetName(newName); - - return true; + return behaviors.RenameBehavior(name, newName); } gd::Behavior& Object::GetBehavior(const gd::String& name) { - return *behaviors.find(name)->second; + return behaviors.GetBehavior(name); } const gd::Behavior& Object::GetBehavior(const gd::String& name) const { - return *behaviors.find(name)->second; + return behaviors.GetBehavior(name); } bool Object::HasBehaviorNamed(const gd::String& name) const { - return behaviors.find(name) != behaviors.end(); + return behaviors.HasBehaviorNamed(name); } gd::Behavior* Object::AddNewBehavior(const gd::Project& project, const gd::String& type, const gd::String& name) { - auto initializeAndAdd = [this, - &name](std::unique_ptr behavior) { - behavior->InitializeContent(); - this->behaviors[name] = std::move(behavior); - return this->behaviors[name].get(); - }; - - if (project.HasEventsBasedBehavior(type)) { - return initializeAndAdd( - gd::make_unique(name, project, type)); - } else { - const gd::BehaviorMetadata& behaviorMetadata = - gd::MetadataProvider::GetBehaviorMetadata(project.GetCurrentPlatform(), - type); - if (gd::MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) { - gd::LogWarning("Tried to create a behavior with an unknown type: " + - type + " on object " + GetName() + "!"); - // It's probably an events-based behavior that was removed. - // Create a custom behavior to preserve the properties values. - return initializeAndAdd( - gd::make_unique(name, project, type)); - } - std::unique_ptr behavior(behaviorMetadata.Get().Clone()); - behavior->SetName(name); - return initializeAndAdd(std::move(behavior)); - } + return behaviors.AddNewBehavior(project, type, name); } void Object::UnserializeFrom(gd::Project& project, @@ -145,7 +107,7 @@ void Object::UnserializeFrom(gd::Project& project, // Compatibility with GD <= 3.3 if (element.HasChild("Automatism")) { for (std::size_t i = 0; i < element.GetChildrenCount("Automatism"); ++i) { - SerializerElement& behaviorElement = element.GetChild("Automatism", i); + SerializerElement &behaviorElement = element.GetChild("Automatism", i); gd::String type = behaviorElement.GetStringAttribute("type", "", "Type") .FindAndReplace("Automatism", "Behavior"); @@ -157,59 +119,9 @@ void Object::UnserializeFrom(gd::Project& project, } // End of compatibility code else { - SerializerElement& behaviorsElement = + SerializerElement &behaviorsElement = element.GetChild("behaviors", 0, "automatisms"); - behaviorsElement.ConsiderAsArrayOf("behavior", "automatism"); - for (std::size_t i = 0; i < behaviorsElement.GetChildrenCount(); ++i) { - SerializerElement& behaviorElement = behaviorsElement.GetChild(i); - - gd::String type = - behaviorElement.GetStringAttribute("type").FindAndReplace( - "Automatism", "Behavior"); // Compatibility with GD <= 4 - gd::String name = behaviorElement.GetStringAttribute("name"); - - auto behavior = gd::Object::AddNewBehavior(project, type, name); - // Compatibility with GD <= 4.0.98 - // If there is only one child called "content" (in addition to "type" and - // "name"), it's the content of a JavaScript behavior. Move the content - // out of the "content" object (to put it directly at the root of the - // behavior element). - if (behaviorElement.HasChild("content") && - behaviorElement.GetAllChildren().size() == 3) { - SerializerElement& contentElement = behaviorElement.GetChild("content"); - - // Physics2 Behavior was using "type" for the type of the body. The name - // conflicts with the behavior "type". Rename it. - if (contentElement.HasChild("type")) { - contentElement.AddChild("bodyType") - .SetValue(contentElement.GetChild("type").GetStringValue()); - contentElement.RemoveChild("type"); - } - - behavior->UnserializeFrom(contentElement); - } - // end of compatibility code - else { - behavior->UnserializeFrom(behaviorElement); - } - - bool isFolded = behaviorElement.GetBoolAttribute("isFolded", false); - behavior->SetFolded(isFolded); - - // Handle Quick Customization info. - if (behaviorElement.HasChild( - "propertiesQuickCustomizationVisibilities")) { - behavior->GetPropertiesQuickCustomizationVisibilities().UnserializeFrom( - behaviorElement.GetChild( - "propertiesQuickCustomizationVisibilities")); - } - if (behaviorElement.HasChild("quickCustomizationVisibility")) { - behavior->SetQuickCustomizationVisibility( - QuickCustomization::StringAsVisibility( - behaviorElement.GetStringAttribute( - "quickCustomizationVisibility"))); - } - } + behaviors.UnserializeFrom(project, behaviorsElement); } configuration->UnserializeFrom(project, element); @@ -224,48 +136,7 @@ void Object::SerializeTo(SerializerElement& element) const { element.SetAttribute("type", GetType()); objectVariables.SerializeTo(element.AddChild("variables")); effectsContainer.SerializeTo(element.AddChild("effects")); - - SerializerElement& behaviorsElement = element.AddChild("behaviors"); - behaviorsElement.ConsiderAsArrayOf("behavior"); - std::vector allBehaviors = GetAllBehaviorNames(); - for (std::size_t i = 0; i < allBehaviors.size(); ++i) { - const gd::Behavior& behavior = GetBehavior(allBehaviors[i]); - // Default behaviors are added at the object creation according to - // metadata. They don't need to be serialized. - // During the export, all behaviors are set as not default by - // `BehaviorDefaultFlagClearer` because the Runtime needs all the behaviors. - if (behavior.IsDefaultBehavior()) { - continue; - } - SerializerElement& behaviorElement = behaviorsElement.AddChild("behavior"); - - behavior.SerializeTo(behaviorElement); - behaviorElement.RemoveChild("type"); // The content can contain type or - // name properties, remove them. - behaviorElement.RemoveChild("name"); - behaviorElement.RemoveChild("isFolded"); - behaviorElement.SetAttribute("type", behavior.GetTypeName()); - behaviorElement.SetAttribute("name", behavior.GetName()); - if (behavior.IsFolded()) behaviorElement.SetAttribute("isFolded", true); - - // Handle Quick Customization info. - behaviorElement.RemoveChild("propertiesQuickCustomizationVisibilities"); - const QuickCustomizationVisibilitiesContainer& - propertiesQuickCustomizationVisibilities = - behavior.GetPropertiesQuickCustomizationVisibilities(); - if (!propertiesQuickCustomizationVisibilities.IsEmpty()) { - propertiesQuickCustomizationVisibilities.SerializeTo( - behaviorElement.AddChild("propertiesQuickCustomizationVisibilities")); - } - const QuickCustomization::Visibility visibility = - behavior.GetQuickCustomizationVisibility(); - if (visibility != QuickCustomization::Visibility::Default) { - behaviorElement.SetAttribute( - "quickCustomizationVisibility", - QuickCustomization::VisibilityAsString(visibility)); - } - } - + behaviors.SerializeTo(element.AddChild("behaviors")); configuration->SerializeTo(element); } diff --git a/Core/GDCore/Project/Object.h b/Core/GDCore/Project/Object.h index a9eaeb354..61ecca6f8 100644 --- a/Core/GDCore/Project/Object.h +++ b/Core/GDCore/Project/Object.h @@ -10,6 +10,7 @@ #include #include "GDCore/Project/Behavior.h" +#include "GDCore/Project/BehaviorsContainer.h" #include "GDCore/Project/EffectsContainer.h" #include "GDCore/Project/ObjectConfiguration.h" #include "GDCore/Project/VariablesContainer.h" @@ -180,7 +181,7 @@ class GD_CORE_API Object { */ const std::map>& GetAllBehaviorContents() const { - return behaviors; + return behaviors.GetAllBehaviorContents(); }; ///@} @@ -253,10 +254,10 @@ class GD_CORE_API Object { gd::String assetStoreId; ///< The ID of the asset if the object comes from ///< the store. std::unique_ptr configuration; - std::map> - behaviors; ///< Contains all behaviors and their properties for the - ///< object. Behavior contents are the ownership of the - ///< object. + gd::BehaviorsContainer + behaviors; ///< Contains all behaviors and their properties for the + ///< object. Behavior contents are the ownership of the + ///< object. gd::VariablesContainer objectVariables; ///< List of the variables of the object gd::EffectsContainer diff --git a/Core/GDCore/Project/Project.cpp b/Core/GDCore/Project/Project.cpp index 38aec183a..d96b48f74 100644 --- a/Core/GDCore/Project/Project.cpp +++ b/Core/GDCore/Project/Project.cpp @@ -915,7 +915,7 @@ void Project::UnserializeFrom(const SerializerElement& element) { gd::ExternalLayout& newExternalLayout = InsertNewExternalLayout("", GetExternalLayoutsCount()); - newExternalLayout.UnserializeFrom(externalLayoutElement); + newExternalLayout.UnserializeFrom(*this, externalLayoutElement); } } diff --git a/Core/GDCore/Serialization/SerializerElement.cpp b/Core/GDCore/Serialization/SerializerElement.cpp index bde56aeed..0478c9622 100644 --- a/Core/GDCore/Serialization/SerializerElement.cpp +++ b/Core/GDCore/Serialization/SerializerElement.cpp @@ -283,6 +283,19 @@ void SerializerElement::RemoveChild(const gd::String& name) { } } +void SerializerElement::Clear() { + children.clear(); + attributes.clear(); +} + +bool SerializerElement::IsEmpty() { + return children.empty() && attributes.empty(); +} + +void SerializerElement::RemoveAttribute(const gd::String& name) { + attributes.erase(name); +} + void SerializerElement::Init(const gd::SerializerElement& other) { valueUndefined = other.valueUndefined; elementValue = other.elementValue; diff --git a/Core/GDCore/Serialization/SerializerElement.h b/Core/GDCore/Serialization/SerializerElement.h index 40614eed2..d0a4adf17 100644 --- a/Core/GDCore/Serialization/SerializerElement.h +++ b/Core/GDCore/Serialization/SerializerElement.h @@ -329,6 +329,12 @@ class GD_CORE_API SerializerElement { */ bool HasAttribute(const gd::String &name) const; + /** + * \brief Remove the attribute with the specified name + * \param name The name of the attribute to remove. + */ + void RemoveAttribute(const gd::String& name); + /** * \brief Return all the attributes of the element. */ @@ -438,6 +444,13 @@ class GD_CORE_API SerializerElement { */ void RemoveChild(const gd::String &name); + /** + * \brief Remove all children and attributes. + */ + void Clear(); + + bool IsEmpty(); + /** * \brief Return all the children of the element. */ diff --git a/Core/tests/DummyPlatform.cpp b/Core/tests/DummyPlatform.cpp index b409b9732..9e888804d 100644 --- a/Core/tests/DummyPlatform.cpp +++ b/Core/tests/DummyPlatform.cpp @@ -20,6 +20,62 @@ #include "GDCore/Extensions/Metadata/ParameterOptions.h" #include "catch.hpp" +class BehaviorWithProperties : public gd::Behavior { + public: + BehaviorWithProperties( + const gd::String& name, const gd::String& type) + : Behavior(name, type) {}; + virtual ~BehaviorWithProperties(){}; + virtual std::unique_ptr Clone() const override { + return gd::make_unique(*this); + } + + virtual std::map GetProperties( + const gd::SerializerElement& behaviorContent) const override { + std::map properties; + properties["numberProperty"] + .SetLabel("Number property") + .SetValue( + gd::String::From(behaviorContent.GetChild("numberProperty").GetDoubleValue())) + .SetType("Number"); + properties["stringProperty"] + .SetLabel("String property") + .SetValue( + behaviorContent.GetChild("stringProperty").GetStringValue()) + .SetType("String"); + properties["booleanProperty"] + .SetLabel("Boolean property") + .SetValue( + behaviorContent.GetChild("booleanProperty").GetBoolValue() ? "true" : "false") + .SetType("Boolean"); + return properties; + } + virtual bool UpdateProperty(gd::SerializerElement& behaviorContent, + const gd::String& name, + const gd::String& value) override { + auto &element = behaviorContent.AddChild(name); + if (name == "numberProperty") { + element.SetDoubleValue(value.To()); + return true; + } + if (name == "stringProperty") { + element.SetStringValue(value); + return true; + } + if (name == "booleanProperty") { + element.SetBoolValue(value == "1"); + return true; + } + return false; + } + virtual void InitializeContent( + gd::SerializerElement& behaviorContent) override { + behaviorContent.AddChild("numberProperty").SetDoubleValue(123); + behaviorContent.AddChild("stringProperty").SetStringValue("ABC"); + behaviorContent.AddChild("booleanProperty").SetBoolValue(true); + } +}; + // TODO Remove these 2 classes and write the test with events based behaviors. class BehaviorWithRequiredBehaviorProperty : public gd::Behavior { public: @@ -564,7 +620,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project, "Group", "Icon.png", "MyBehavior", - gd::make_unique( + gd::make_unique( "Behavior", "MyExtension::MyBehavior"), gd::make_unique()); behavior diff --git a/Core/tests/InitialInstance.cpp b/Core/tests/InitialInstance.cpp index 588f02ab6..a49966bf9 100644 --- a/Core/tests/InitialInstance.cpp +++ b/Core/tests/InitialInstance.cpp @@ -12,18 +12,109 @@ #include #include +#include "DummyPlatform.h" #include "GDCore/CommonTools.h" +#include "GDCore/Extensions/Platform.h" #include "GDCore/Project/InitialInstance.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Object.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Project/PropertyDescriptor.h" #include "GDCore/Tools/VersionWrapper.h" TEST_CASE("InitialInstance", "[common][instances]") { - gd::InitialInstance instance; SECTION("GetRawDoubleProperty") { + gd::InitialInstance instance; REQUIRE(instance.GetRawDoubleProperty("NotExistingProperty") == 0); } SECTION("GetRawStringProperty") { + gd::InitialInstance instance; REQUIRE(instance.GetRawStringProperty("NotExistingProperty") == ""); } + + SECTION("Can create an instance without behavior property overriding") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + + gd::Layout &layout = project.InsertNewLayout("Scene", 0); + gd::Object &object = layout.GetObjects().InsertNewObject( + project, "MyExtension::FakeObjectWithDefaultBehavior", "MyObject", 0); + object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior"); + REQUIRE(object.HasBehaviorNamed("MyBehavior") == true); + gd::Behavior &behavior = object.GetBehavior("MyBehavior"); + + gd::InitialInstance instance; + instance.SetObjectName("MyObject"); + REQUIRE(instance.HasAnyOverriddenProperty(object) == false); + REQUIRE(instance.HasAnyOverriddenPropertyForBehavior(behavior) == false); + REQUIRE(instance.HasBehaviorOverridingNamed("MyBehavior") == false); + } + + SECTION("Can add and remove behavior property overridings on an instance") { + gd::Platform platform; + gd::Project project; + SetupProjectWithDummyPlatform(project, platform); + + gd::Layout &layout = project.InsertNewLayout("Scene", 0); + gd::Object &object = layout.GetObjects().InsertNewObject( + project, "MyExtension::FakeObjectWithDefaultBehavior", "MyObject", 0); + object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior"); + REQUIRE(object.HasBehaviorNamed("MyBehavior") == true); + gd::Behavior &behavior = object.GetBehavior("MyBehavior"); + + gd::InitialInstance instance; + instance.SetObjectName("MyObject"); + instance.AddNewBehaviorOverriding(project, "MyExtension::MyBehavior", + "MyBehavior"); + REQUIRE(instance.HasBehaviorOverridingNamed("MyBehavior") == true); + gd::Behavior &behaviorOverriding = + instance.GetBehaviorOverriding("MyBehavior"); + + // The behavior overriding is empty by default. + REQUIRE(instance.HasAnyOverriddenProperty(object) == false); + REQUIRE(instance.HasAnyOverriddenPropertyForBehavior(behavior) == false); + REQUIRE(behaviorOverriding.HasPropertyValue("numberProperty") == false); + REQUIRE(behaviorOverriding.HasPropertyValue("stringProperty") == false); + REQUIRE(behaviorOverriding.HasPropertyValue("booleanProperty") == false); + + // Add some behavior property overridings + behaviorOverriding.UpdateProperty("numberProperty", "456"); + REQUIRE( + behaviorOverriding.GetProperties().at("numberProperty").GetValue() == + "456"); + REQUIRE(behaviorOverriding.HasPropertyValue("numberProperty") == true); + REQUIRE(instance.HasAnyOverriddenProperty(object) == true); + REQUIRE(instance.HasAnyOverriddenPropertyForBehavior(behavior) == true); + + behaviorOverriding.UpdateProperty("stringProperty", "DEF"); + REQUIRE( + behaviorOverriding.GetProperties().at("stringProperty").GetValue() == + "DEF"); + REQUIRE(behaviorOverriding.HasPropertyValue("stringProperty") == true); + + behaviorOverriding.UpdateProperty("booleanProperty", "false"); + REQUIRE(behaviorOverriding.HasPropertyValue("booleanProperty") == true); + REQUIRE( + behaviorOverriding.GetProperties().at("booleanProperty").GetValue() == + "false"); + + // The user set back original values from the object behavior. + // The IDE removes the behavior property overridings accordingly. + behaviorOverriding.RemoveProperty("numberProperty"); + REQUIRE(behaviorOverriding.HasPropertyValue("numberProperty") == false); + behaviorOverriding.RemoveProperty("stringProperty"); + REQUIRE(behaviorOverriding.HasPropertyValue("stringProperty") == false); + behaviorOverriding.RemoveProperty("booleanProperty"); + REQUIRE(behaviorOverriding.HasPropertyValue("booleanProperty") == false); + + // No more property is overriden + REQUIRE(instance.HasAnyOverriddenPropertyForBehavior(behavior) == false); + REQUIRE(instance.HasAnyOverriddenProperty(object) == false); + + // The IDE removes the behavior overriding accordingly. + instance.RemoveBehaviorOverriding("MyBehavior"); + } } diff --git a/Extensions/3D/A_RuntimeObject3D.ts b/Extensions/3D/A_RuntimeObject3D.ts index 652580947..d2adee49d 100644 --- a/Extensions/3D/A_RuntimeObject3D.ts +++ b/Extensions/3D/A_RuntimeObject3D.ts @@ -70,9 +70,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: gdjs.Object3DData + objectData: gdjs.Object3DData, + instanceData: InstanceData | undefined ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); // TODO Should 0 be replaced by 0.01 instead of using the default value? this._width = this._originalWidth = getValidDimensionValue( objectData.content.width diff --git a/Extensions/3D/Base3DBehavior.ts b/Extensions/3D/Base3DBehavior.ts index 860da413c..a0e5e6b2a 100644 --- a/Extensions/3D/Base3DBehavior.ts +++ b/Extensions/3D/Base3DBehavior.ts @@ -174,7 +174,7 @@ namespace gdjs { this.object = owner; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/Extensions/3D/Cube3DRuntimeObject.ts b/Extensions/3D/Cube3DRuntimeObject.ts index 1ffd1cffb..762405645 100644 --- a/Extensions/3D/Cube3DRuntimeObject.ts +++ b/Extensions/3D/Cube3DRuntimeObject.ts @@ -84,9 +84,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: Cube3DObjectData + objectData: Cube3DObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._shouldUseTransparentTexture = objectData.content.enableTextureTransparency || false; this._facesOrientation = objectData.content.facesOrientation || 'Y'; diff --git a/Extensions/3D/CustomRuntimeObject3D.ts b/Extensions/3D/CustomRuntimeObject3D.ts index ca6f94210..32e4db6f1 100644 --- a/Extensions/3D/CustomRuntimeObject3D.ts +++ b/Extensions/3D/CustomRuntimeObject3D.ts @@ -44,9 +44,10 @@ namespace gdjs { constructor( parent: gdjs.RuntimeInstanceContainer, - objectData: gdjs.Object3DData & gdjs.CustomObjectConfiguration + objectData: gdjs.Object3DData & gdjs.CustomObjectConfiguration, + instanceData?: InstanceData ) { - super(parent, objectData); + super(parent, objectData, instanceData); } protected override _createRender() { diff --git a/Extensions/3D/Model3DRuntimeObject.ts b/Extensions/3D/Model3DRuntimeObject.ts index 483f96473..5c6aba48f 100644 --- a/Extensions/3D/Model3DRuntimeObject.ts +++ b/Extensions/3D/Model3DRuntimeObject.ts @@ -114,9 +114,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: Model3DObjectData + objectData: Model3DObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._data = objectData; this._modelResourceName = objectData.content.modelResourceName; this._animations = objectData.content.animations; diff --git a/Extensions/AnchorBehavior/anchorruntimebehavior.ts b/Extensions/AnchorBehavior/anchorruntimebehavior.ts index 33dc202e6..c06844b58 100644 --- a/Extensions/AnchorBehavior/anchorruntimebehavior.ts +++ b/Extensions/AnchorBehavior/anchorruntimebehavior.ts @@ -68,32 +68,24 @@ namespace gdjs { : behaviorData.useLegacyBottomAndRightAnchors; } - override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.leftEdgeAnchor !== newBehaviorData.leftEdgeAnchor) { - this._leftEdgeAnchor = newBehaviorData.leftEdgeAnchor; + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.leftEdgeAnchor !== undefined) { + this._leftEdgeAnchor = behaviorData.leftEdgeAnchor; } - if (oldBehaviorData.rightEdgeAnchor !== newBehaviorData.rightEdgeAnchor) { - this._rightEdgeAnchor = newBehaviorData.rightEdgeAnchor; + if (behaviorData.rightEdgeAnchor !== undefined) { + this._rightEdgeAnchor = behaviorData.rightEdgeAnchor; } - if (oldBehaviorData.topEdgeAnchor !== newBehaviorData.topEdgeAnchor) { - this._topEdgeAnchor = newBehaviorData.topEdgeAnchor; + if (behaviorData.topEdgeAnchor !== undefined) { + this._topEdgeAnchor = behaviorData.topEdgeAnchor; } - if ( - oldBehaviorData.bottomEdgeAnchor !== newBehaviorData.bottomEdgeAnchor - ) { - this._bottomEdgeAnchor = newBehaviorData.bottomEdgeAnchor; + if (behaviorData.bottomEdgeAnchor !== undefined) { + this._bottomEdgeAnchor = behaviorData.bottomEdgeAnchor; } - if ( - oldBehaviorData.useLegacyTrajectory !== - newBehaviorData.useLegacyTrajectory - ) { + if (behaviorData.useLegacyTrajectory !== undefined) { this._useLegacyBottomAndRightAnchors = - newBehaviorData.useLegacyBottomAndRightAnchors; + behaviorData.useLegacyBottomAndRightAnchors; } - if ( - oldBehaviorData.relativeToOriginalWindowSize !== - newBehaviorData.relativeToOriginalWindowSize - ) { + if (behaviorData.relativeToOriginalWindowSize !== undefined) { return false; } return true; diff --git a/Extensions/AnchorBehavior/tests/AnchorRuntimeBehaviorInCustomObject.spec.js b/Extensions/AnchorBehavior/tests/AnchorRuntimeBehaviorInCustomObject.spec.js index aaacc7221..8cc51270a 100644 --- a/Extensions/AnchorBehavior/tests/AnchorRuntimeBehaviorInCustomObject.spec.js +++ b/Extensions/AnchorBehavior/tests/AnchorRuntimeBehaviorInCustomObject.spec.js @@ -1,6 +1,6 @@ // @ts-check describe('gdjs.AnchorRuntimeBehavior', () => { - it('can fill a custom object with an child', async () => { + it('can fill a custom object with a child', async () => { const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets(); const runtimeScene = new gdjs.TestRuntimeScene(runtimeGame); // The corresponding event-based object declaration is done by @@ -44,4 +44,109 @@ describe('gdjs.AnchorRuntimeBehavior', () => { expect(childObject.getWidth()).to.equal(2000); expect(childObject.getHeight()).to.equal(3000); }); + + it('can anchor 2 instances of the same object left and right', async () => { + const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets({ + customObjectInstances: [ + { + angle: 0, + customSize: true, + height: 64, + layer: '', + name: 'MySprite', + persistentUuid: '668db48d-4e12-4b6f-aa6b-f73b74bf608e', + width: 32, + x: 0, + y: 0, + zOrder: 1, + numberProperties: [], + stringProperties: [], + initialVariables: [], + behaviorOverridings: [ + // Magnet on the left + { + name: 'AnchorBehavior', + type: 'AnchorBehavior::AnchorBehavior', + rightEdgeAnchor: 0, + }, + ], + }, + { + angle: 0, + customSize: true, + height: 64, + layer: '', + name: 'MySprite', + persistentUuid: '668db48d-4e12-4b6f-aa6b-f73b74bf608e', + width: 32, + x: 32, + y: 0, + zOrder: 1, + numberProperties: [], + stringProperties: [], + initialVariables: [], + behaviorOverridings: [ + // Magnet on the right + { + name: 'AnchorBehavior', + type: 'AnchorBehavior::AnchorBehavior', + leftEdgeAnchor: 0, + }, + ], + }, + ], + }); + const runtimeScene = new gdjs.TestRuntimeScene(runtimeGame); + // The corresponding event-based object declaration is done by + // getPixiRuntimeGame. + const customObject = new gdjs.CustomRuntimeObject2D(runtimeScene, { + name: 'MyCustomObject', + type: 'MyExtension::MyLayoutedEventsBasedObject', + variant: '', + variables: [], + behaviors: [], + effects: [], + content: {}, + childrenContent: {}, + isInnerAreaFollowingParentSize: false, + }); + runtimeScene.addObject(customObject); + customObject.setPosition(500, 250); + + const childObjects = customObject + .getChildrenContainer() + .getObjects('MySprite'); + if (!childObjects || childObjects.length === 0) { + throw new Error("Can't get child objects."); + } + const leftChildObject = childObjects[0]; + const rightChildObject = childObjects[1]; + + runtimeScene.renderAndStep(1000 / 60); + + // The child object keeps its initial location. + expect(leftChildObject.getX()).to.equal(0); + expect(leftChildObject.getY()).to.equal(0); + expect(leftChildObject.getWidth()).to.equal(32); + expect(leftChildObject.getHeight()).to.equal(64); + + expect(rightChildObject.getX()).to.equal(32); + expect(rightChildObject.getY()).to.equal(0); + expect(rightChildObject.getWidth()).to.equal(32); + expect(rightChildObject.getHeight()).to.equal(64); + + customObject.setWidth(200); + customObject.setHeight(300); + runtimeScene.renderAndStep(1000 / 60); + + expect(leftChildObject.getX()).to.equal(0); + expect(leftChildObject.getY()).to.equal(0); + expect(leftChildObject.getWidth()).to.equal(32); + expect(leftChildObject.getHeight()).to.equal(300); + + expect(rightChildObject.getX()).to.equal(32 + 200 - 64); + expect(rightChildObject.getY()).to.equal(0); + expect(rightChildObject.getWidth()).to.equal(32); + expect(rightChildObject.getHeight()).to.equal(300); + }); }); diff --git a/Extensions/BBText/bbtextruntimeobject.ts b/Extensions/BBText/bbtextruntimeobject.ts index b73b1a0aa..05437b713 100644 --- a/Extensions/BBText/bbtextruntimeobject.ts +++ b/Extensions/BBText/bbtextruntimeobject.ts @@ -87,9 +87,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: BBTextObjectData + objectData: BBTextObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); // @ts-ignore - parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string. this._opacity = parseFloat(objectData.content.opacity); this._text = objectData.content.text; diff --git a/Extensions/BitmapText/bitmaptextruntimeobject.ts b/Extensions/BitmapText/bitmaptextruntimeobject.ts index a4177ccc1..9fdf1ce01 100644 --- a/Extensions/BitmapText/bitmaptextruntimeobject.ts +++ b/Extensions/BitmapText/bitmaptextruntimeobject.ts @@ -88,9 +88,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: BitmapTextObjectData + objectData: BitmapTextObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._opacity = objectData.content.opacity; this._text = objectData.content.text; diff --git a/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts b/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts index 0016843d4..590d3b957 100644 --- a/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts +++ b/Extensions/DestroyOutsideBehavior/destroyoutsideruntimebehavior.ts @@ -24,15 +24,12 @@ namespace gdjs { this._hasBeenOnScreen = false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) { - this._extraBorder = newBehaviorData.extraBorder; + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.extraBorder !== undefined) { + this._extraBorder = behaviorData.extraBorder; } - if ( - oldBehaviorData.unseenGraceDistance !== - newBehaviorData.unseenGraceDistance - ) { - this._unseenGraceDistance = newBehaviorData.unseenGraceDistance; + if (behaviorData.unseenGraceDistance !== undefined) { + this._unseenGraceDistance = behaviorData.unseenGraceDistance; } return true; } diff --git a/Extensions/DraggableBehavior/draggableruntimebehavior.ts b/Extensions/DraggableBehavior/draggableruntimebehavior.ts index 7cf552fa2..330b55b10 100644 --- a/Extensions/DraggableBehavior/draggableruntimebehavior.ts +++ b/Extensions/DraggableBehavior/draggableruntimebehavior.ts @@ -27,7 +27,7 @@ namespace gdjs { this._checkCollisionMask = behaviorData.checkCollisionMask ? true : false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/Extensions/ExampleJsExtension/dummyruntimebehavior.ts b/Extensions/ExampleJsExtension/dummyruntimebehavior.ts index 29d9e0932..1435472a7 100644 --- a/Extensions/ExampleJsExtension/dummyruntimebehavior.ts +++ b/Extensions/ExampleJsExtension/dummyruntimebehavior.ts @@ -26,9 +26,9 @@ namespace gdjs { logger.log('DummyRuntimeBehavior was created for object:', owner); } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.property1 !== newBehaviorData.property1) { - this._textToSet = newBehaviorData.property1; + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.property1 !== undefined) { + this._textToSet = behaviorData.property1; } // Return true to specify that the new behavior data have been applied diff --git a/Extensions/ExampleJsExtension/dummyruntimeobject.ts b/Extensions/ExampleJsExtension/dummyruntimeobject.ts index f0214953e..bc45c1651 100644 --- a/Extensions/ExampleJsExtension/dummyruntimeobject.ts +++ b/Extensions/ExampleJsExtension/dummyruntimeobject.ts @@ -15,9 +15,13 @@ namespace gdjs { // @ts-expect-error ts-migrate(2564) FIXME: Property 'opacity' has no initializer and is not d... Remove this comment to see the full error message opacity: float; - constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) { + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + objectData, + instanceData?: InstanceData + ) { // *ALWAYS* call the base gdjs.RuntimeObject constructor. - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._property1 = objectData.content.property1; this._renderer = new gdjs.DummyRuntimeObjectRenderer( this, diff --git a/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts b/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts index adad4cf62..75f07dd8f 100644 --- a/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts +++ b/Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.ts @@ -32,9 +32,9 @@ namespace gdjs { logger.log('The shared data are:', sharedData); } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.property1 !== newBehaviorData.property1) { - this._textToSet = newBehaviorData.property1; + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.property1 !== undefined) { + this._textToSet = behaviorData.property1; } // Return true to specify that the new behavior data have been applied diff --git a/Extensions/Lighting/lightruntimeobject.ts b/Extensions/Lighting/lightruntimeobject.ts index 1e0e7c642..a278f064b 100644 --- a/Extensions/Lighting/lightruntimeobject.ts +++ b/Extensions/Lighting/lightruntimeobject.ts @@ -52,9 +52,10 @@ namespace gdjs { constructor( runtimeScene: gdjs.RuntimeScene, - lightObjectData: LightObjectData + lightObjectData: LightObjectData, + instanceData?: InstanceData ) { - super(runtimeScene, lightObjectData); + super(runtimeScene, lightObjectData, instanceData); this._radius = lightObjectData.content.radius > 0 ? lightObjectData.content.radius : 1; this._color = gdjs.rgbOrHexToRGBColor(lightObjectData.content.color); diff --git a/Extensions/LinkedObjects/tests/linkedobjects.spec.js b/Extensions/LinkedObjects/tests/linkedobjects.spec.js index 44b5614c0..9a07fa7c4 100644 --- a/Extensions/LinkedObjects/tests/linkedobjects.spec.js +++ b/Extensions/LinkedObjects/tests/linkedobjects.spec.js @@ -54,76 +54,112 @@ describe('gdjs.LinksManager', function () { const manager = gdjs.LinksManager.getManager(runtimeScene); - const object1A = new gdjs.RuntimeObject(runtimeScene, { - name: object1Names[0], - type: '', - behaviors: [], - variables: [], - effects: [], - }); - const object1B = new gdjs.RuntimeObject(runtimeScene, { - name: object1Names[1], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object1A = new gdjs.RuntimeObject( + runtimeScene, + { + name: object1Names[0], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); + const object1B = new gdjs.RuntimeObject( + runtimeScene, + { + name: object1Names[1], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object1C = new gdjs.RuntimeObject(runtimeScene, { - name: object1Names[2], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object1C = new gdjs.RuntimeObject( + runtimeScene, + { + name: object1Names[2], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object2A = new gdjs.RuntimeObject(runtimeScene, { - name: object2Names[0], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object2A = new gdjs.RuntimeObject( + runtimeScene, + { + name: object2Names[0], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object2B = new gdjs.RuntimeObject(runtimeScene, { - name: object2Names[1], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object2B = new gdjs.RuntimeObject( + runtimeScene, + { + name: object2Names[1], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object2C = new gdjs.RuntimeObject(runtimeScene, { - name: object2Names[2], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object2C = new gdjs.RuntimeObject( + runtimeScene, + { + name: object2Names[2], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object3A = new gdjs.RuntimeObject(runtimeScene, { - name: object3Names[0], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object3A = new gdjs.RuntimeObject( + runtimeScene, + { + name: object3Names[0], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object3B = new gdjs.RuntimeObject(runtimeScene, { - name: object3Names[1], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object3B = new gdjs.RuntimeObject( + runtimeScene, + { + name: object3Names[1], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); - const object3C = new gdjs.RuntimeObject(runtimeScene, { - name: object3Names[2], - type: '', - behaviors: [], - variables: [], - effects: [], - }); + const object3C = new gdjs.RuntimeObject( + runtimeScene, + { + name: object3Names[2], + type: '', + behaviors: [], + variables: [], + effects: [], + }, + undefined + ); runtimeScene.addObject(object1A); runtimeScene.addObject(object1B); diff --git a/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts b/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts index 1e33da4d6..813c9f3b3 100644 --- a/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts +++ b/Extensions/PanelSpriteObject/panelspriteruntimeobject.ts @@ -74,9 +74,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - panelSpriteObjectData: PanelSpriteObjectData + panelSpriteObjectData: PanelSpriteObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, panelSpriteObjectData); + super(instanceContainer, panelSpriteObjectData, instanceData); this._objectData = panelSpriteObjectData; this._rBorder = panelSpriteObjectData.rightMargin; this._lBorder = panelSpriteObjectData.leftMargin; diff --git a/Extensions/ParticleSystem/particleemitterobject.ts b/Extensions/ParticleSystem/particleemitterobject.ts index 62f99b351..cae8cca61 100644 --- a/Extensions/ParticleSystem/particleemitterobject.ts +++ b/Extensions/ParticleSystem/particleemitterobject.ts @@ -180,9 +180,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - particleObjectData: ParticleEmitterObjectData + particleObjectData: ParticleEmitterObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, particleObjectData); + super(instanceContainer, particleObjectData, instanceData); this._renderer = new gdjs.ParticleEmitterObjectRenderer( instanceContainer, this, diff --git a/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts b/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts index 4b3493d66..94852e75c 100644 --- a/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts +++ b/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.ts @@ -126,12 +126,12 @@ namespace gdjs { //The owner is not yet fully constructed. } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.impassable !== newBehaviorData.impassable) { - this.setImpassable(newBehaviorData.impassable); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.impassable !== undefined) { + this.setImpassable(behaviorData.impassable); } - if (oldBehaviorData.cost !== newBehaviorData.cost) { - this.setCost(newBehaviorData.cost); + if (behaviorData.cost !== undefined) { + this.setCost(behaviorData.cost); } return true; } diff --git a/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts b/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts index bb84b2689..045b0f9fd 100644 --- a/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts +++ b/Extensions/PathfindingBehavior/pathfindingruntimebehavior.ts @@ -93,45 +93,42 @@ namespace gdjs { ); } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.allowDiagonals !== newBehaviorData.allowDiagonals) { - this.allowDiagonals(newBehaviorData.allowDiagonals); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.allowDiagonals !== undefined) { + this.allowDiagonals(behaviorData.allowDiagonals); } - if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) { - this.setAcceleration(newBehaviorData.acceleration); + if (behaviorData.acceleration !== undefined) { + this.setAcceleration(behaviorData.acceleration); } - if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) { - this.setMaxSpeed(newBehaviorData.maxSpeed); + if (behaviorData.maxSpeed !== undefined) { + this.setMaxSpeed(behaviorData.maxSpeed); } - if (oldBehaviorData.angularMaxSpeed !== newBehaviorData.angularMaxSpeed) { - this.setAngularMaxSpeed(newBehaviorData.angularMaxSpeed); + if (behaviorData.angularMaxSpeed !== undefined) { + this.setAngularMaxSpeed(behaviorData.angularMaxSpeed); } - if (oldBehaviorData.rotateObject !== newBehaviorData.rotateObject) { - this.setRotateObject(newBehaviorData.rotateObject); + if (behaviorData.rotateObject !== undefined) { + this.setRotateObject(behaviorData.rotateObject); } - if (oldBehaviorData.angleOffset !== newBehaviorData.angleOffset) { - this.setAngleOffset(newBehaviorData.angleOffset); + if (behaviorData.angleOffset !== undefined) { + this.setAngleOffset(behaviorData.angleOffset); } - if (oldBehaviorData.cellWidth !== newBehaviorData.cellWidth) { - this.setCellWidth(newBehaviorData.cellWidth); + if (behaviorData.cellWidth !== undefined) { + this.setCellWidth(behaviorData.cellWidth); } - if (oldBehaviorData.cellHeight !== newBehaviorData.cellHeight) { - this.setCellHeight(newBehaviorData.cellHeight); + if (behaviorData.cellHeight !== undefined) { + this.setCellHeight(behaviorData.cellHeight); } - if (oldBehaviorData.gridOffsetX !== newBehaviorData.gridOffsetX) { - this._gridOffsetX = newBehaviorData.gridOffsetX; + if (behaviorData.gridOffsetX !== undefined) { + this._gridOffsetX = behaviorData.gridOffsetX; } - if (oldBehaviorData.gridOffsetY !== newBehaviorData.gridOffsetY) { - this._gridOffsetY = newBehaviorData.gridOffsetY; + if (behaviorData.gridOffsetY !== undefined) { + this._gridOffsetY = behaviorData.gridOffsetY; } - if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) { - this.setExtraBorder(newBehaviorData.extraBorder); + if (behaviorData.extraBorder !== undefined) { + this.setExtraBorder(behaviorData.extraBorder); } - if ( - oldBehaviorData.smoothingMaxCellGap !== - newBehaviorData.smoothingMaxCellGap - ) { - this._smoothingMaxCellGap = newBehaviorData.smoothingMaxCellGap; + if (behaviorData.smoothingMaxCellGap !== undefined) { + this._smoothingMaxCellGap = behaviorData.smoothingMaxCellGap; } return true; } diff --git a/Extensions/PathfindingBehavior/tests/FrameRatePathfindingRuntimeBehavior.spec.js b/Extensions/PathfindingBehavior/tests/FrameRatePathfindingRuntimeBehavior.spec.js index 87049ceff..c96a140fd 100644 --- a/Extensions/PathfindingBehavior/tests/FrameRatePathfindingRuntimeBehavior.spec.js +++ b/Extensions/PathfindingBehavior/tests/FrameRatePathfindingRuntimeBehavior.spec.js @@ -62,29 +62,33 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { }; const addPlayer = (runtimeScene, allowDiagonals) => { - const player = new gdjs.RuntimeObject(runtimeScene, { - name: 'player', - type: '', - behaviors: [ - { - type: 'PathfindingBehavior::PathfindingBehavior', - name: 'auto1', - // @ts-ignore - properties are not typed - allowDiagonals: allowDiagonals, - acceleration: 400, - maxSpeed: 200, - angularMaxSpeed: 180, - rotateObject: false, - angleOffset: 0, - cellWidth: 20, - cellHeight: 20, - extraBorder: 0, - collisionMethod: true, - }, - ], - effects: [], - variables: [], - }); + const player = new gdjs.RuntimeObject( + runtimeScene, + { + name: 'player', + type: '', + behaviors: [ + { + type: 'PathfindingBehavior::PathfindingBehavior', + name: 'auto1', + // @ts-ignore - properties are not typed + allowDiagonals: allowDiagonals, + acceleration: 400, + maxSpeed: 200, + angularMaxSpeed: 180, + rotateObject: false, + angleOffset: 0, + cellWidth: 20, + cellHeight: 20, + extraBorder: 0, + collisionMethod: true, + }, + ], + effects: [], + variables: [], + }, + undefined + ); player.getWidth = function () { return 90; }; @@ -96,20 +100,24 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { }; const addObstacle = (runtimeScene) => { - const obstacle = new gdjs.RuntimeObject(runtimeScene, { - name: 'obstacle', - type: '', - behaviors: [ - { - type: 'PathfindingBehavior::PathfindingObstacleBehavior', - // @ts-ignore - properties are not typed - impassable: true, - cost: 2, - }, - ], - effects: [], - variables: [], - }); + const obstacle = new gdjs.RuntimeObject( + runtimeScene, + { + name: 'obstacle', + type: '', + behaviors: [ + { + type: 'PathfindingBehavior::PathfindingObstacleBehavior', + // @ts-ignore - properties are not typed + impassable: true, + cost: 2, + }, + ], + effects: [], + variables: [], + }, + undefined + ); obstacle.getWidth = function () { return 100; }; diff --git a/Extensions/PathfindingBehavior/tests/commonpathfindingruntimebehavior.spec.js b/Extensions/PathfindingBehavior/tests/commonpathfindingruntimebehavior.spec.js index 28fbe6e87..3ca6ff37c 100644 --- a/Extensions/PathfindingBehavior/tests/commonpathfindingruntimebehavior.spec.js +++ b/Extensions/PathfindingBehavior/tests/commonpathfindingruntimebehavior.spec.js @@ -65,30 +65,34 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { }; const addPlayer = (runtimeScene) => { - const player = new gdjs.RuntimeObject(runtimeScene, { - name: 'player', - type: '', - behaviors: [ - { - type: 'PathfindingBehavior::PathfindingBehavior', - name: 'auto1', - // @ts-ignore - properties are not typed - allowDiagonals: allowDiagonals, - acceleration: 400, - maxSpeed: 200, - angularMaxSpeed: 180, - rotateObject: false, - angleOffset: 0, - cellWidth: 20, - cellHeight: 20, - extraBorder: 0, - smoothingMaxCellGap: smoothingMaxCellGap, - collisionMethod: collisionMethod, - }, - ], - effects: [], - variables: [], - }); + const player = new gdjs.RuntimeObject( + runtimeScene, + { + name: 'player', + type: '', + behaviors: [ + { + type: 'PathfindingBehavior::PathfindingBehavior', + name: 'auto1', + // @ts-ignore - properties are not typed + allowDiagonals: allowDiagonals, + acceleration: 400, + maxSpeed: 200, + angularMaxSpeed: 180, + rotateObject: false, + angleOffset: 0, + cellWidth: 20, + cellHeight: 20, + extraBorder: 0, + smoothingMaxCellGap: smoothingMaxCellGap, + collisionMethod: collisionMethod, + }, + ], + effects: [], + variables: [], + }, + undefined + ); player.getWidth = function () { return 90; }; @@ -100,20 +104,24 @@ describe('gdjs.PathfindingRuntimeBehavior', function () { }; const addObstacle = (runtimeScene) => { - const obstacle = new gdjs.RuntimeObject(runtimeScene, { - name: 'obstacle', - type: '', - behaviors: [ - { - type: 'PathfindingBehavior::PathfindingObstacleBehavior', - // @ts-ignore - properties are not typed - impassable: true, - cost: 2, - }, - ], - effects: [], - variables: [], - }); + const obstacle = new gdjs.RuntimeObject( + runtimeScene, + { + name: 'obstacle', + type: '', + behaviors: [ + { + type: 'PathfindingBehavior::PathfindingObstacleBehavior', + // @ts-ignore - properties are not typed + impassable: true, + cost: 2, + }, + ], + effects: [], + variables: [], + }, + undefined + ); obstacle.getWidth = function () { return 100; }; diff --git a/Extensions/Physics2Behavior/physics2runtimebehavior.ts b/Extensions/Physics2Behavior/physics2runtimebehavior.ts index c6647d96c..3ac33586e 100644 --- a/Extensions/Physics2Behavior/physics2runtimebehavior.ts +++ b/Extensions/Physics2Behavior/physics2runtimebehavior.ts @@ -443,69 +443,69 @@ namespace gdjs { return tempb2Vec2Sec; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.bullet !== newBehaviorData.bullet) { - this.setBullet(newBehaviorData.bullet); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.bullet !== undefined) { + this.setBullet(behaviorData.bullet); } - if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) { - this.setFixedRotation(newBehaviorData.fixedRotation); + if (behaviorData.fixedRotation !== undefined) { + this.setFixedRotation(behaviorData.fixedRotation); } - if (oldBehaviorData.canSleep !== newBehaviorData.canSleep) { - this.setSleepingAllowed(newBehaviorData.canSleep); + if (behaviorData.canSleep !== undefined) { + this.setSleepingAllowed(behaviorData.canSleep); } - if (oldBehaviorData.shapeDimensionA !== newBehaviorData.shapeDimensionA) { - this.shapeDimensionA = newBehaviorData.shapeDimensionA; + if (behaviorData.shapeDimensionA !== undefined) { + this.shapeDimensionA = behaviorData.shapeDimensionA; this.recreateShape(); } - if (oldBehaviorData.shapeDimensionB !== newBehaviorData.shapeDimensionB) { - this.shapeDimensionB = newBehaviorData.shapeDimensionB; + if (behaviorData.shapeDimensionB !== undefined) { + this.shapeDimensionB = behaviorData.shapeDimensionB; this.recreateShape(); } - if (oldBehaviorData.shapeOffsetX !== newBehaviorData.shapeOffsetX) { - this.shapeOffsetX = newBehaviorData.shapeOffsetX; + if (behaviorData.shapeOffsetX !== undefined) { + this.shapeOffsetX = behaviorData.shapeOffsetX; this.recreateShape(); } - if (oldBehaviorData.shapeOffsetY !== newBehaviorData.shapeOffsetY) { - this.shapeOffsetY = newBehaviorData.shapeOffsetY; + if (behaviorData.shapeOffsetY !== undefined) { + this.shapeOffsetY = behaviorData.shapeOffsetY; this.recreateShape(); } - if (oldBehaviorData.polygonOrigin !== newBehaviorData.polygonOrigin) { - this.polygonOrigin = newBehaviorData.polygonOrigin; + if (behaviorData.polygonOrigin !== undefined) { + this.polygonOrigin = behaviorData.polygonOrigin; this.recreateShape(); } - if (oldBehaviorData.density !== newBehaviorData.density) { - this.setDensity(newBehaviorData.density); + if (behaviorData.density !== undefined) { + this.setDensity(behaviorData.density); } - if (oldBehaviorData.friction !== newBehaviorData.friction) { - this.setFriction(newBehaviorData.friction); + if (behaviorData.friction !== undefined) { + this.setFriction(behaviorData.friction); } - if (oldBehaviorData.restitution !== newBehaviorData.restitution) { - this.setRestitution(newBehaviorData.restitution); + if (behaviorData.restitution !== undefined) { + this.setRestitution(behaviorData.restitution); } - if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) { - this.setLinearDamping(newBehaviorData.linearDamping); + if (behaviorData.linearDamping !== undefined) { + this.setLinearDamping(behaviorData.linearDamping); } - if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) { - this.setAngularDamping(newBehaviorData.angularDamping); + if (behaviorData.angularDamping !== undefined) { + this.setAngularDamping(behaviorData.angularDamping); } - if (oldBehaviorData.gravityScale !== newBehaviorData.gravityScale) { - this.setGravityScale(newBehaviorData.gravityScale); + if (behaviorData.gravityScale !== undefined) { + this.setGravityScale(behaviorData.gravityScale); } // TODO: make these properties updatable. - if (oldBehaviorData.layers !== newBehaviorData.layers) { + if (behaviorData.layers !== undefined) { return false; } - if (oldBehaviorData.masks !== newBehaviorData.masks) { + if (behaviorData.masks !== undefined) { return false; } - if (oldBehaviorData.vertices !== newBehaviorData.vertices) { + if (behaviorData.vertices !== undefined) { return false; } - if (oldBehaviorData.bodyType !== newBehaviorData.bodyType) { + if (behaviorData.bodyType !== undefined) { return false; } - if (oldBehaviorData.shape !== newBehaviorData.shape) { + if (behaviorData.shape !== undefined) { return false; } return true; diff --git a/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts b/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts index 0142cc8d6..daf0272e9 100644 --- a/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts +++ b/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts @@ -459,54 +459,54 @@ namespace gdjs { return tempQuat; } - override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.bullet !== newBehaviorData.bullet) { - this.setBullet(newBehaviorData.bullet); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.bullet !== undefined) { + this.setBullet(behaviorData.bullet); } - if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) { - this.setFixedRotation(newBehaviorData.fixedRotation); + if (behaviorData.fixedRotation !== undefined) { + this.setFixedRotation(behaviorData.fixedRotation); } - if (oldBehaviorData.shapeDimensionA !== newBehaviorData.shapeDimensionA) { - this.shapeDimensionA = newBehaviorData.shapeDimensionA; + if (behaviorData.shapeDimensionA !== undefined) { + this.shapeDimensionA = behaviorData.shapeDimensionA; this._needToRecreateShape = true; } - if (oldBehaviorData.shapeDimensionB !== newBehaviorData.shapeDimensionB) { - this.shapeDimensionB = newBehaviorData.shapeDimensionB; + if (behaviorData.shapeDimensionB !== undefined) { + this.shapeDimensionB = behaviorData.shapeDimensionB; this._needToRecreateShape = true; } - if (oldBehaviorData.density !== newBehaviorData.density) { - this.setDensity(newBehaviorData.density); + if (behaviorData.density !== undefined) { + this.setDensity(behaviorData.density); } - if (oldBehaviorData.friction !== newBehaviorData.friction) { - this.setFriction(newBehaviorData.friction); + if (behaviorData.friction !== undefined) { + this.setFriction(behaviorData.friction); } - if (oldBehaviorData.restitution !== newBehaviorData.restitution) { - this.setRestitution(newBehaviorData.restitution); + if (behaviorData.restitution !== undefined) { + this.setRestitution(behaviorData.restitution); } - if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) { - this.setLinearDamping(newBehaviorData.linearDamping); + if (behaviorData.linearDamping !== undefined) { + this.setLinearDamping(behaviorData.linearDamping); } - if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) { - this.setAngularDamping(newBehaviorData.angularDamping); + if (behaviorData.angularDamping !== undefined) { + this.setAngularDamping(behaviorData.angularDamping); } - if (oldBehaviorData.gravityScale !== newBehaviorData.gravityScale) { - this.setGravityScale(newBehaviorData.gravityScale); + if (behaviorData.gravityScale !== undefined) { + this.setGravityScale(behaviorData.gravityScale); } // TODO: make these properties updatable. - if (oldBehaviorData.layers !== newBehaviorData.layers) { + if (behaviorData.layers !== undefined) { return false; } - if (oldBehaviorData.masks !== newBehaviorData.masks) { + if (behaviorData.masks !== undefined) { return false; } - if (oldBehaviorData.vertices !== newBehaviorData.vertices) { + if (behaviorData.vertices !== undefined) { return false; } - if (oldBehaviorData.bodyType !== newBehaviorData.bodyType) { + if (behaviorData.bodyType !== undefined) { return false; } - if (oldBehaviorData.shape !== newBehaviorData.shape) { + if (behaviorData.shape !== undefined) { return false; } return true; diff --git a/Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.ts b/Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.ts index 40179018c..6e66ec3a8 100644 --- a/Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.ts +++ b/Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.ts @@ -212,97 +212,78 @@ namespace gdjs { return this._physics3D; } - override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.steerAngleMax !== newBehaviorData.steerAngleMax) { - this.setSteerAngleMax(newBehaviorData.steerAngleMax); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.steerAngleMax !== undefined) { + this.setSteerAngleMax(behaviorData.steerAngleMax); } - if ( - oldBehaviorData.beginningSteerSpeed !== - newBehaviorData.beginningSteerSpeed - ) { - this.setBeginningSteerSpeed(newBehaviorData.beginningSteerSpeed); + if (behaviorData.beginningSteerSpeed !== undefined) { + this.setBeginningSteerSpeed(behaviorData.beginningSteerSpeed); } - if (oldBehaviorData.endSteerSpeed !== newBehaviorData.endSteerSpeed) { - this.setEndSteerSpeed(newBehaviorData.endSteerSpeed); + if (behaviorData.endSteerSpeed !== undefined) { + this.setEndSteerSpeed(behaviorData.endSteerSpeed); } - if (oldBehaviorData.engineTorqueMax !== newBehaviorData.engineTorqueMax) { - this.setEngineTorqueMax(newBehaviorData.engineTorqueMax); + if (behaviorData.engineTorqueMax !== undefined) { + this.setEngineTorqueMax(behaviorData.engineTorqueMax); } - if (oldBehaviorData.engineSpeedMax !== newBehaviorData.engineSpeedMax) { - this.setEngineSpeedMax(newBehaviorData.engineSpeedMax); + if (behaviorData.engineSpeedMax !== undefined) { + this.setEngineSpeedMax(behaviorData.engineSpeedMax); } - if (oldBehaviorData.engineInertia !== newBehaviorData.engineInertia) { - this.setEngineInertia(newBehaviorData.engineInertia); + if (behaviorData.engineInertia !== undefined) { + this.setEngineInertia(behaviorData.engineInertia); } - if ( - oldBehaviorData.reverseGearRatio1 !== newBehaviorData.reverseGearRatio1 - ) { - this.setGearRatio(-1, newBehaviorData.reverseGearRatio1); + if (behaviorData.reverseGearRatio1 !== undefined) { + this.setGearRatio(-1, behaviorData.reverseGearRatio1); } - if (oldBehaviorData.gearRatio1 !== newBehaviorData.gearRatio1) { - this.setGearRatio(1, newBehaviorData.gearRatio1); + if (behaviorData.gearRatio1 !== undefined) { + this.setGearRatio(1, behaviorData.gearRatio1); } - if (oldBehaviorData.gearRatio2 !== newBehaviorData.gearRatio2) { - this.setGearRatio(2, newBehaviorData.gearRatio2); + if (behaviorData.gearRatio2 !== undefined) { + this.setGearRatio(2, behaviorData.gearRatio2); } - if (oldBehaviorData.gearRatio3 !== newBehaviorData.gearRatio3) { - this.setGearRatio(3, newBehaviorData.gearRatio3); + if (behaviorData.gearRatio3 !== undefined) { + this.setGearRatio(3, behaviorData.gearRatio3); } - if (oldBehaviorData.gearRatio4 !== newBehaviorData.gearRatio4) { - this.setGearRatio(4, newBehaviorData.gearRatio4); + if (behaviorData.gearRatio4 !== undefined) { + this.setGearRatio(4, behaviorData.gearRatio4); } - if (oldBehaviorData.gearRatio5 !== newBehaviorData.gearRatio5) { - this.setGearRatio(5, newBehaviorData.gearRatio5); + if (behaviorData.gearRatio5 !== undefined) { + this.setGearRatio(5, behaviorData.gearRatio5); } - if (oldBehaviorData.gearRatio6 !== newBehaviorData.gearRatio6) { - this.setGearRatio(6, newBehaviorData.gearRatio6); + if (behaviorData.gearRatio6 !== undefined) { + this.setGearRatio(6, behaviorData.gearRatio6); } - if ( - oldBehaviorData.backWheelOffsetX !== newBehaviorData.backWheelOffsetX - ) { - this.setBackWheelOffsetX(newBehaviorData.backWheelOffsetX); + if (behaviorData.backWheelOffsetX !== undefined) { + this.setBackWheelOffsetX(behaviorData.backWheelOffsetX); } - if ( - oldBehaviorData.frontWheelOffsetX !== newBehaviorData.frontWheelOffsetX - ) { - this.setFrontWheelOffsetX(newBehaviorData.frontWheelOffsetX); + if (behaviorData.frontWheelOffsetX !== undefined) { + this.setFrontWheelOffsetX(behaviorData.frontWheelOffsetX); } - if (oldBehaviorData.wheelOffsetY !== newBehaviorData.wheelOffsetY) { - this.setWheelOffsetY(newBehaviorData.wheelOffsetY); + if (behaviorData.wheelOffsetY !== undefined) { + this.setWheelOffsetY(behaviorData.wheelOffsetY); } - if (oldBehaviorData.wheelOffsetZ !== newBehaviorData.wheelOffsetZ) { - this.setWheelOffsetZ(newBehaviorData.wheelOffsetZ); + if (behaviorData.wheelOffsetZ !== undefined) { + this.setWheelOffsetZ(behaviorData.wheelOffsetZ); } - if (oldBehaviorData.wheelRadius !== newBehaviorData.wheelRadius) { - this.setWheelRadius(newBehaviorData.wheelRadius); + if (behaviorData.wheelRadius !== undefined) { + this.setWheelRadius(behaviorData.wheelRadius); } - if (oldBehaviorData.wheelWidth !== newBehaviorData.wheelWidth) { - this.setWheelWidth(newBehaviorData.wheelWidth); + if (behaviorData.wheelWidth !== undefined) { + this.setWheelWidth(behaviorData.wheelWidth); } - if (oldBehaviorData.brakeTorqueMax !== newBehaviorData.brakeTorqueMax) { - this.setBrakeTorqueMax(newBehaviorData.brakeTorqueMax); + if (behaviorData.brakeTorqueMax !== undefined) { + this.setBrakeTorqueMax(behaviorData.brakeTorqueMax); } - if ( - oldBehaviorData.handBrakeTorqueMax !== - newBehaviorData.handBrakeTorqueMax - ) { - this.setHandBrakeTorqueMax(newBehaviorData.handBrakeTorqueMax); + if (behaviorData.handBrakeTorqueMax !== undefined) { + this.setHandBrakeTorqueMax(behaviorData.handBrakeTorqueMax); } - if ( - oldBehaviorData.hasBackWheelDrive !== newBehaviorData.hasBackWheelDrive - ) { - this.setBackWheelDrive(newBehaviorData.hasBackWheelDrive); + if (behaviorData.hasBackWheelDrive !== undefined) { + this.setBackWheelDrive(behaviorData.hasBackWheelDrive); } - if ( - oldBehaviorData.hasFrontWheelDrive !== - newBehaviorData.hasFrontWheelDrive - ) { - this.setFrontWheelDrive(newBehaviorData.hasFrontWheelDrive); + if (behaviorData.hasFrontWheelDrive !== undefined) { + this.setFrontWheelDrive(behaviorData.hasFrontWheelDrive); } - if ( - oldBehaviorData.pitchRollAngleMax !== newBehaviorData.pitchRollAngleMax - ) { - this.setPitchRollAngleMax(newBehaviorData.pitchRollAngleMax); + if (behaviorData.pitchRollAngleMax !== undefined) { + this.setPitchRollAngleMax(behaviorData.pitchRollAngleMax); } return true; } diff --git a/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts b/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts index 451e4d2a2..5e983170a 100644 --- a/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts +++ b/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts @@ -263,61 +263,44 @@ namespace gdjs { return this._physics3D; } - override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.gravity !== newBehaviorData.gravity) { - this.setGravity(newBehaviorData.gravity); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.gravity !== undefined) { + this.setGravity(behaviorData.gravity); } - if (oldBehaviorData.maxFallingSpeed !== newBehaviorData.maxFallingSpeed) { - this.setMaxFallingSpeed(newBehaviorData.maxFallingSpeed); + if (behaviorData.maxFallingSpeed !== undefined) { + this.setMaxFallingSpeed(behaviorData.maxFallingSpeed); } - if ( - oldBehaviorData.forwardAcceleration !== - newBehaviorData.forwardAcceleration - ) { - this.setForwardAcceleration(newBehaviorData.forwardAcceleration); + if (behaviorData.forwardAcceleration !== undefined) { + this.setForwardAcceleration(behaviorData.forwardAcceleration); } - if ( - oldBehaviorData.forwardDeceleration !== - newBehaviorData.forwardDeceleration - ) { - this.setForwardDeceleration(newBehaviorData.forwardDeceleration); + if (behaviorData.forwardDeceleration !== undefined) { + this.setForwardDeceleration(behaviorData.forwardDeceleration); } - if (oldBehaviorData.forwardSpeedMax !== newBehaviorData.forwardSpeedMax) { - this.setForwardSpeedMax(newBehaviorData.forwardSpeedMax); + if (behaviorData.forwardSpeedMax !== undefined) { + this.setForwardSpeedMax(behaviorData.forwardSpeedMax); } - if ( - oldBehaviorData.sidewaysAcceleration !== - newBehaviorData.sidewaysAcceleration - ) { - this.setSidewaysAcceleration(newBehaviorData.sidewaysAcceleration); + if (behaviorData.sidewaysAcceleration !== undefined) { + this.setSidewaysAcceleration(behaviorData.sidewaysAcceleration); } - if ( - oldBehaviorData.sidewaysDeceleration !== - newBehaviorData.sidewaysDeceleration - ) { - this.setSidewaysDeceleration(newBehaviorData.sidewaysDeceleration); + if (behaviorData.sidewaysDeceleration !== undefined) { + this.setSidewaysDeceleration(behaviorData.sidewaysDeceleration); } - if ( - oldBehaviorData.sidewaysSpeedMax !== newBehaviorData.sidewaysSpeedMax - ) { - this.setSidewaysSpeedMax(newBehaviorData.sidewaysSpeedMax); + if (behaviorData.sidewaysSpeedMax !== undefined) { + this.setSidewaysSpeedMax(behaviorData.sidewaysSpeedMax); } - if (oldBehaviorData.jumpSustainTime !== newBehaviorData.jumpSustainTime) { - this.setJumpSustainTime(newBehaviorData.jumpSustainTime); + if (behaviorData.jumpSustainTime !== undefined) { + this.setJumpSustainTime(behaviorData.jumpSustainTime); } - if (oldBehaviorData.jumpHeight !== newBehaviorData.jumpHeight) { - this.setJumpSpeed(this.getJumpSpeedToReach(newBehaviorData.jumpHeight)); + if (behaviorData.jumpHeight !== undefined) { + this.setJumpSpeed(this.getJumpSpeedToReach(behaviorData.jumpHeight)); } - if ( - oldBehaviorData.shouldBindObjectAndForwardAngle !== - newBehaviorData.shouldBindObjectAndForwardAngle - ) { + if (behaviorData.shouldBindObjectAndForwardAngle !== undefined) { this.setShouldBindObjectAndForwardAngle( - newBehaviorData.shouldBindObjectAndForwardAngle + behaviorData.shouldBindObjectAndForwardAngle ); } - if (oldBehaviorData.stairHeightMax !== newBehaviorData.stairHeightMax) { - this.setStairHeightMax(newBehaviorData.stairHeightMax); + if (behaviorData.stairHeightMax !== undefined) { + this.setStairHeightMax(behaviorData.stairHeightMax); } return true; } diff --git a/Extensions/PhysicsBehavior/physicsruntimebehavior.ts b/Extensions/PhysicsBehavior/physicsruntimebehavior.ts index e1abc9e22..9e6a48693 100644 --- a/Extensions/PhysicsBehavior/physicsruntimebehavior.ts +++ b/Extensions/PhysicsBehavior/physicsruntimebehavior.ts @@ -181,29 +181,29 @@ namespace gdjs { } //See b2Vec2 method - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.dynamic !== newBehaviorData.dynamic) { - if (newBehaviorData.dynamic) { + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.dynamic !== undefined) { + if (behaviorData.dynamic) { this.setDynamic(); } else { this.setStatic(); } } - if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) { - this.setAngularDamping(newBehaviorData.angularDamping); + if (behaviorData.angularDamping !== undefined) { + this.setAngularDamping(behaviorData.angularDamping); } - if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) { - this.setLinearDamping(newBehaviorData.linearDamping); + if (behaviorData.linearDamping !== undefined) { + this.setLinearDamping(behaviorData.linearDamping); } - if (oldBehaviorData.isBullet !== newBehaviorData.isBullet) { - if (newBehaviorData.isBullet) { + if (behaviorData.isBullet !== undefined) { + if (behaviorData.isBullet) { this.setAsBullet(); } else { this.dontSetAsBullet(); } } - if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) { - if (newBehaviorData.fixedRotation) { + if (behaviorData.fixedRotation !== undefined) { + if (behaviorData.fixedRotation) { this.setFixedRotation(); } else { this.setFreeRotation(); @@ -211,19 +211,16 @@ namespace gdjs { } // TODO: make these properties updatable. - if (oldBehaviorData.massDensity !== newBehaviorData.massDensity) { + if (behaviorData.massDensity !== undefined) { return false; } - if (oldBehaviorData.averageFriction !== newBehaviorData.averageFriction) { + if (behaviorData.averageFriction !== undefined) { return false; } - if ( - oldBehaviorData.averageRestitution !== - newBehaviorData.averageRestitution - ) { + if (behaviorData.averageRestitution !== undefined) { return false; } - if (oldBehaviorData.shapeType !== newBehaviorData.shapeType) { + if (behaviorData.shapeType !== undefined) { return false; } return true; diff --git a/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts b/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts index a57e02138..22803a67e 100644 --- a/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts +++ b/Extensions/PlatformBehavior/platformerobjectruntimebehavior.ts @@ -368,56 +368,45 @@ namespace gdjs { this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.gravity !== newBehaviorData.gravity) { - this.setGravity(newBehaviorData.gravity); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.gravity !== undefined) { + this.setGravity(behaviorData.gravity); } - if (oldBehaviorData.maxFallingSpeed !== newBehaviorData.maxFallingSpeed) { - this.setMaxFallingSpeed(newBehaviorData.maxFallingSpeed); + if (behaviorData.maxFallingSpeed !== undefined) { + this.setMaxFallingSpeed(behaviorData.maxFallingSpeed); } - if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) { - this.setAcceleration(newBehaviorData.acceleration); + if (behaviorData.acceleration !== undefined) { + this.setAcceleration(behaviorData.acceleration); } - if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) { - this.setDeceleration(newBehaviorData.deceleration); + if (behaviorData.deceleration !== undefined) { + this.setDeceleration(behaviorData.deceleration); } - if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) { - this.setMaxSpeed(newBehaviorData.maxSpeed); + if (behaviorData.maxSpeed !== undefined) { + this.setMaxSpeed(behaviorData.maxSpeed); } - if (oldBehaviorData.jumpSpeed !== newBehaviorData.jumpSpeed) { - this.setJumpSpeed(newBehaviorData.jumpSpeed); + if (behaviorData.jumpSpeed !== undefined) { + this.setJumpSpeed(behaviorData.jumpSpeed); } - if ( - oldBehaviorData.canGrabPlatforms !== newBehaviorData.canGrabPlatforms - ) { - this.setCanGrabPlatforms(newBehaviorData.canGrabPlatforms); + if (behaviorData.canGrabPlatforms !== undefined) { + this.setCanGrabPlatforms(behaviorData.canGrabPlatforms); } - if ( - oldBehaviorData.canGrabWithoutMoving !== - newBehaviorData.canGrabWithoutMoving - ) { - this._canGrabWithoutMoving = newBehaviorData.canGrabWithoutMoving; + if (behaviorData.canGrabWithoutMoving !== undefined) { + this._canGrabWithoutMoving = behaviorData.canGrabWithoutMoving; } - if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) { - this._yGrabOffset = newBehaviorData.yGrabOffset; + if (behaviorData.yGrabOffset !== undefined) { + this._yGrabOffset = behaviorData.yGrabOffset; } - if (oldBehaviorData.xGrabTolerance !== newBehaviorData.xGrabTolerance) { - this._xGrabTolerance = newBehaviorData.xGrabTolerance; + if (behaviorData.xGrabTolerance !== undefined) { + this._xGrabTolerance = behaviorData.xGrabTolerance; } - if (oldBehaviorData.jumpSustainTime !== newBehaviorData.jumpSustainTime) { - this.setJumpSustainTime(newBehaviorData.jumpSustainTime); + if (behaviorData.jumpSustainTime !== undefined) { + this.setJumpSustainTime(behaviorData.jumpSustainTime); } - if ( - oldBehaviorData.useLegacyTrajectory !== - newBehaviorData.useLegacyTrajectory - ) { - this._useLegacyTrajectory = newBehaviorData.useLegacyTrajectory; + if (behaviorData.useLegacyTrajectory !== undefined) { + this._useLegacyTrajectory = behaviorData.useLegacyTrajectory; } - if ( - oldBehaviorData.canGoDownFromJumpthru !== - newBehaviorData.canGoDownFromJumpthru - ) { - this._canGoDownFromJumpthru = newBehaviorData.canGoDownFromJumpthru; + if (behaviorData.canGoDownFromJumpthru !== undefined) { + this._canGoDownFromJumpthru = behaviorData.canGoDownFromJumpthru; } return true; } diff --git a/Extensions/PlatformBehavior/platformruntimebehavior.ts b/Extensions/PlatformBehavior/platformruntimebehavior.ts index 78be79d30..c66e418b7 100644 --- a/Extensions/PlatformBehavior/platformruntimebehavior.ts +++ b/Extensions/PlatformBehavior/platformruntimebehavior.ts @@ -146,15 +146,15 @@ namespace gdjs { this._manager = PlatformObjectsManager.getManager(instanceContainer); } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.platformType !== newBehaviorData.platformType) { - this.changePlatformType(newBehaviorData.platformType); + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.platformType !== undefined) { + this.changePlatformType(behaviorData.platformType); } - if (oldBehaviorData.canBeGrabbed !== newBehaviorData.canBeGrabbed) { - this._canBeGrabbed = newBehaviorData.canBeGrabbed; + if (behaviorData.canBeGrabbed !== undefined) { + this._canBeGrabbed = behaviorData.canBeGrabbed; } - if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) { - this._yGrabOffset = newBehaviorData.yGrabOffset; + if (behaviorData.yGrabOffset !== undefined) { + this._yGrabOffset = behaviorData.yGrabOffset; } return true; } diff --git a/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts b/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts index 4251053b0..87946410b 100644 --- a/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts +++ b/Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts @@ -100,9 +100,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - shapePainterObjectData: ShapePainterObjectData + shapePainterObjectData: ShapePainterObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, shapePainterObjectData); + super(instanceContainer, shapePainterObjectData, instanceData); this._fillColor = typeof shapePainterObjectData.fillColor === 'string' ? gdjs.rgbOrHexStringToNumber(shapePainterObjectData.fillColor) diff --git a/Extensions/Spine/spineruntimeobject.ts b/Extensions/Spine/spineruntimeobject.ts index 28a91f1f6..d1877a793 100644 --- a/Extensions/Spine/spineruntimeobject.ts +++ b/Extensions/Spine/spineruntimeobject.ts @@ -68,9 +68,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: SpineObjectData + objectData: SpineObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._animations = objectData.content.animations; this._originalScale = objectData.content.scale; diff --git a/Extensions/TextEntryObject/textentryruntimeobject.ts b/Extensions/TextEntryObject/textentryruntimeobject.ts index 3ff0f8184..441f7e13c 100644 --- a/Extensions/TextEntryObject/textentryruntimeobject.ts +++ b/Extensions/TextEntryObject/textentryruntimeobject.ts @@ -19,9 +19,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - textEntryObjectData: ObjectData + textEntryObjectData: ObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, textEntryObjectData); + super(instanceContainer, textEntryObjectData, instanceData); this._renderer = new gdjs.TextEntryRuntimeObjectRenderer(this); // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. diff --git a/Extensions/TextInput/textinputruntimeobject.ts b/Extensions/TextInput/textinputruntimeobject.ts index e4cfd3056..80be7646f 100644 --- a/Extensions/TextInput/textinputruntimeobject.ts +++ b/Extensions/TextInput/textinputruntimeobject.ts @@ -134,9 +134,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: TextInputObjectData + objectData: TextInputObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._string = objectData.content.initialValue; this._placeholder = objectData.content.placeholder; diff --git a/Extensions/TextObject/textruntimeobject.ts b/Extensions/TextObject/textruntimeobject.ts index 92bdb0816..339c35938 100644 --- a/Extensions/TextObject/textruntimeobject.ts +++ b/Extensions/TextObject/textruntimeobject.ts @@ -134,9 +134,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - textObjectData: TextObjectData + textObjectData: TextObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, textObjectData); + super(instanceContainer, textObjectData, instanceData); const content = textObjectData.content; this._characterSize = Math.max(1, content.characterSize); this._fontName = content.font; diff --git a/Extensions/TileMap/simpletilemapruntimeobject.ts b/Extensions/TileMap/simpletilemapruntimeobject.ts index 49a50ac17..65fc206d4 100644 --- a/Extensions/TileMap/simpletilemapruntimeobject.ts +++ b/Extensions/TileMap/simpletilemapruntimeobject.ts @@ -71,9 +71,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: ObjectData & SimpleTileMapObjectDataType + objectData: ObjectData & SimpleTileMapObjectDataType, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._atlasImage = objectData.content.atlasImage; this._rowCount = objectData.content.rowCount; this._columnCount = objectData.content.columnCount; diff --git a/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts b/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts index 4e8d821a7..519204c30 100644 --- a/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts +++ b/Extensions/TileMap/tilemapcollisionmaskruntimeobject.ts @@ -88,8 +88,12 @@ namespace gdjs { */ private _transformationIsUpToDate: boolean = false; - constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) { - super(instanceContainer, objectData); + constructor( + instanceContainer: gdjs.RuntimeInstanceContainer, + objectData, + instanceData?: InstanceData + ) { + super(instanceContainer, objectData, instanceData); this._tilemapJsonFile = objectData.content.tilemapJsonFile; this._tilesetJsonFile = objectData.content.tilesetJsonFile; this._collisionMaskTag = objectData.content.collisionMaskTag; diff --git a/Extensions/TileMap/tilemapruntimeobject.ts b/Extensions/TileMap/tilemapruntimeobject.ts index a6e964a46..104c8c0cc 100644 --- a/Extensions/TileMap/tilemapruntimeobject.ts +++ b/Extensions/TileMap/tilemapruntimeobject.ts @@ -65,9 +65,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: TilemapObjectData + objectData: TilemapObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._tilemapJsonFile = objectData.content.tilemapJsonFile; this._tilesetJsonFile = objectData.content.tilesetJsonFile; this._tilemapAtlasImage = objectData.content.tilemapAtlasImage; diff --git a/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts b/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts index 6aa22b592..7925b0c84 100644 --- a/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts +++ b/Extensions/TiledSpriteObject/tiledspriteruntimeobject.ts @@ -63,9 +63,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - tiledSpriteObjectData: TiledSpriteObjectData + tiledSpriteObjectData: TiledSpriteObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, tiledSpriteObjectData); + super(instanceContainer, tiledSpriteObjectData, instanceData); this._objectData = tiledSpriteObjectData; this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer( this, diff --git a/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts b/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts index deea3582b..305c9374c 100644 --- a/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts +++ b/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.ts @@ -184,49 +184,42 @@ namespace gdjs { this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { - if (oldBehaviorData.allowDiagonals !== newBehaviorData.allowDiagonals) { - this._allowDiagonals = newBehaviorData.allowDiagonals; + override applyBehaviorOverriding(behaviorData): boolean { + if (behaviorData.allowDiagonals !== undefined) { + this._allowDiagonals = behaviorData.allowDiagonals; } - if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) { - this._acceleration = newBehaviorData.acceleration; + if (behaviorData.acceleration !== undefined) { + this._acceleration = behaviorData.acceleration; } - if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) { - this._deceleration = newBehaviorData.deceleration; + if (behaviorData.deceleration !== undefined) { + this._deceleration = behaviorData.deceleration; } - if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) { - this._maxSpeed = newBehaviorData.maxSpeed; + if (behaviorData.maxSpeed !== undefined) { + this._maxSpeed = behaviorData.maxSpeed; } - if (oldBehaviorData.angularMaxSpeed !== newBehaviorData.angularMaxSpeed) { - this._angularMaxSpeed = newBehaviorData.angularMaxSpeed; + if (behaviorData.angularMaxSpeed !== undefined) { + this._angularMaxSpeed = behaviorData.angularMaxSpeed; } - if (oldBehaviorData.rotateObject !== newBehaviorData.rotateObject) { - this._rotateObject = newBehaviorData.rotateObject; + if (behaviorData.rotateObject !== undefined) { + this._rotateObject = behaviorData.rotateObject; } - if (oldBehaviorData.angleOffset !== newBehaviorData.angleOffset) { - this._angleOffset = newBehaviorData.angleOffset; + if (behaviorData.angleOffset !== undefined) { + this._angleOffset = behaviorData.angleOffset; + } + if (behaviorData.ignoreDefaultControls !== undefined) { + this._ignoreDefaultControls = behaviorData.ignoreDefaultControls; } if ( - oldBehaviorData.ignoreDefaultControls !== - newBehaviorData.ignoreDefaultControls - ) { - this._ignoreDefaultControls = newBehaviorData.ignoreDefaultControls; - } - if ( - oldBehaviorData.platformType !== newBehaviorData.platformType || - oldBehaviorData.customIsometryAngle !== - newBehaviorData.customIsometryAngle + behaviorData.platformType !== undefined || + behaviorData.customIsometryAngle !== undefined ) { this.setViewpoint( - newBehaviorData.platformType, - newBehaviorData.customIsometryAngle + behaviorData.platformType, + behaviorData.customIsometryAngle ); } - if ( - oldBehaviorData.movementAngleOffset !== - newBehaviorData.movementAngleOffset - ) { - this._movementAngleOffset = newBehaviorData.movementAngleOffset; + if (behaviorData.movementAngleOffset !== undefined) { + this._movementAngleOffset = behaviorData.movementAngleOffset; } return true; } diff --git a/Extensions/TweenBehavior/tweenruntimebehavior.ts b/Extensions/TweenBehavior/tweenruntimebehavior.ts index b547a764e..c4f3c180e 100644 --- a/Extensions/TweenBehavior/tweenruntimebehavior.ts +++ b/Extensions/TweenBehavior/tweenruntimebehavior.ts @@ -390,10 +390,7 @@ namespace gdjs { super(instanceContainer, behaviorData, owner); } - updateFromBehaviorData( - oldBehaviorData: BehaviorData, - newBehaviorData: BehaviorData - ): boolean { + override applyBehaviorOverriding(behaviorData: BehaviorData): boolean { // Nothing to update. return true; } diff --git a/Extensions/Video/videoruntimeobject.ts b/Extensions/Video/videoruntimeobject.ts index be9b7d081..852e63d2b 100644 --- a/Extensions/Video/videoruntimeobject.ts +++ b/Extensions/Video/videoruntimeobject.ts @@ -70,9 +70,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - videoObjectData: VideoObjectData + videoObjectData: VideoObjectData, + instanceData?: InstanceData ) { - super(instanceContainer, videoObjectData); + super(instanceContainer, videoObjectData, instanceData); this._opacity = videoObjectData.content.opacity; this._loop = videoObjectData.content.loop; this._volume = videoObjectData.content.volume; diff --git a/GDJS/GDJS/Events/CodeGeneration/BehaviorCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/BehaviorCodeGenerator.cpp index 669722e96..67ae28f5b 100644 --- a/GDJS/GDJS/Events/CodeGeneration/BehaviorCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/BehaviorCodeGenerator.cpp @@ -207,7 +207,7 @@ CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME = class RUNTIME_BEHAVIOR_CLASSNAME ext } // Hot-reload: - updateFromBehaviorData(oldBehaviorData, newBehaviorData) { + applyBehaviorOverriding(behaviorOverriding) { UPDATE_FROM_BEHAVIOR_DATA_CODE return true; @@ -396,8 +396,8 @@ gd::String BehaviorCodeGenerator::GenerateUpdatePropertyFromBehaviorDataCode( const gd::EventsBasedBehavior& eventsBasedBehavior, const gd::NamedPropertyDescriptor& property) { return gd::String(R"jscode_template( - if (oldBehaviorData.PROPERTY_NAME !== newBehaviorData.PROPERTY_NAME) - this._behaviorData.PROPERTY_NAME = newBehaviorData.PROPERTY_NAME;)jscode_template") + if (behaviorOverriding.PROPERTY_NAME !== undefined) + this._behaviorData.PROPERTY_NAME = behaviorOverriding.PROPERTY_NAME;)jscode_template") .FindAndReplace("PROPERTY_NAME", property.GetName()); } diff --git a/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp index 4913690d6..109c78fc7 100644 --- a/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/ObjectCodeGenerator.cpp @@ -204,8 +204,8 @@ CODE_NAMESPACE = CODE_NAMESPACE || {}; * Object generated from OBJECT_FULL_NAME */ CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends RUNTIME_OBJECT_BASE_CLASS_NAME { - constructor(parentInstanceContainer, objectData) { - super(parentInstanceContainer, objectData); + constructor(parentInstanceContainer, objectData, instanceData) { + super(parentInstanceContainer, objectData, instanceData); this._parentInstanceContainer = parentInstanceContainer; this._onceTriggers = new gdjs.OnceTriggers(); diff --git a/GDJS/Runtime/CustomRuntimeObject.ts b/GDJS/Runtime/CustomRuntimeObject.ts index 802194d55..12079d94d 100644 --- a/GDJS/Runtime/CustomRuntimeObject.ts +++ b/GDJS/Runtime/CustomRuntimeObject.ts @@ -103,9 +103,10 @@ namespace gdjs { */ constructor( parent: gdjs.RuntimeInstanceContainer, - objectData: ObjectData & CustomObjectConfiguration + objectData: ObjectData & CustomObjectConfiguration, + instanceData: InstanceData | undefined ) { - super(parent, objectData); + super(parent, objectData, instanceData); this._type = objectData.type; this._instanceContainer = new gdjs.CustomRuntimeObjectInstanceContainer( parent, diff --git a/GDJS/Runtime/CustomRuntimeObject2D.ts b/GDJS/Runtime/CustomRuntimeObject2D.ts index 8ed804c38..2ad087250 100644 --- a/GDJS/Runtime/CustomRuntimeObject2D.ts +++ b/GDJS/Runtime/CustomRuntimeObject2D.ts @@ -6,9 +6,10 @@ namespace gdjs { export class CustomRuntimeObject2D extends gdjs.CustomRuntimeObject { constructor( parent: gdjs.RuntimeInstanceContainer, - objectData: ObjectData & CustomObjectConfiguration + objectData: ObjectData & CustomObjectConfiguration, + instanceData?: InstanceData ) { - super(parent, objectData); + super(parent, objectData, instanceData); } protected override _createRender(): gdjs.CustomRuntimeObject2DRenderer { diff --git a/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts b/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts index d56670ebc..5860ce866 100644 --- a/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts +++ b/GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts @@ -70,8 +70,11 @@ namespace gdjs { this._orderedLayers.length = 0; } - createObject(objectName: string): gdjs.RuntimeObject | null { - const result = super.createObject(objectName); + override createObject( + objectName: string, + instanceData?: InstanceData + ): gdjs.RuntimeObject | null { + const result = super.createObject(objectName, instanceData); this._customObject.onChildrenLocationChanged(); return result; } diff --git a/GDJS/Runtime/InGameEditor/InGameEditor.tsx b/GDJS/Runtime/InGameEditor/InGameEditor.tsx index 0dadecfc2..ebfecdaab 100644 --- a/GDJS/Runtime/InGameEditor/InGameEditor.tsx +++ b/GDJS/Runtime/InGameEditor/InGameEditor.tsx @@ -5071,9 +5071,10 @@ namespace gdjs { constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: gdjs.Object3DData + objectData: gdjs.Object3DData, + instanceData?: InstanceData ) { - super(instanceContainer, objectData); + super(instanceContainer, objectData, instanceData); this._renderer = new UnknownRuntimeObjectRenderer( this, instanceContainer diff --git a/GDJS/Runtime/RuntimeInstanceContainer.ts b/GDJS/Runtime/RuntimeInstanceContainer.ts index 00c21f6fd..5a70e9cd2 100644 --- a/GDJS/Runtime/RuntimeInstanceContainer.ts +++ b/GDJS/Runtime/RuntimeInstanceContainer.ts @@ -357,7 +357,7 @@ namespace gdjs { continue; } - const newObject = this.createObject(objectName); + const newObject = this.createObject(objectName, instanceData); if (newObject !== null) { if (shouldTrackByPersistentUuid) { // Give the object the same persistentUuid as the instance, so that @@ -660,7 +660,10 @@ namespace gdjs { * @param objectName The name of the object to be created * @return The created object */ - createObject(objectName: string): gdjs.RuntimeObject | null { + createObject( + objectName: string, + instanceData?: InstanceData + ): gdjs.RuntimeObject | null { if ( !this._objectsCtor.containsKey(objectName) || !this._objects.containsKey(objectName) @@ -685,7 +688,7 @@ namespace gdjs { const ctor = this._objectsCtor.get(objectName); let obj; if (!cache || cache.length === 0) { - obj = new ctor(this, objectData); + obj = new ctor(this, objectData, instanceData); } else { // Reuse an objet destroyed before. If there is an object in the cache, // then it means it does support reinitialization. diff --git a/GDJS/Runtime/object-capabilities/AnimatableBehavior.ts b/GDJS/Runtime/object-capabilities/AnimatableBehavior.ts index 0e8be66d9..6a24c2c2a 100644 --- a/GDJS/Runtime/object-capabilities/AnimatableBehavior.ts +++ b/GDJS/Runtime/object-capabilities/AnimatableBehavior.ts @@ -91,7 +91,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/object-capabilities/EffectBehavior.ts b/GDJS/Runtime/object-capabilities/EffectBehavior.ts index 19fe288f0..c505c24b3 100644 --- a/GDJS/Runtime/object-capabilities/EffectBehavior.ts +++ b/GDJS/Runtime/object-capabilities/EffectBehavior.ts @@ -80,7 +80,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/object-capabilities/FlippableBehavior.ts b/GDJS/Runtime/object-capabilities/FlippableBehavior.ts index 61e43b87b..d16964e62 100644 --- a/GDJS/Runtime/object-capabilities/FlippableBehavior.ts +++ b/GDJS/Runtime/object-capabilities/FlippableBehavior.ts @@ -38,7 +38,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/object-capabilities/OpacityBehavior.ts b/GDJS/Runtime/object-capabilities/OpacityBehavior.ts index 742794e63..1f4b16201 100644 --- a/GDJS/Runtime/object-capabilities/OpacityBehavior.ts +++ b/GDJS/Runtime/object-capabilities/OpacityBehavior.ts @@ -42,7 +42,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/object-capabilities/ResizableBehavior.ts b/GDJS/Runtime/object-capabilities/ResizableBehavior.ts index be1e733a2..461a4fdd6 100644 --- a/GDJS/Runtime/object-capabilities/ResizableBehavior.ts +++ b/GDJS/Runtime/object-capabilities/ResizableBehavior.ts @@ -64,7 +64,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/object-capabilities/ScalableBehavior.ts b/GDJS/Runtime/object-capabilities/ScalableBehavior.ts index 1c98fb1f0..fbbd76588 100644 --- a/GDJS/Runtime/object-capabilities/ScalableBehavior.ts +++ b/GDJS/Runtime/object-capabilities/ScalableBehavior.ts @@ -74,7 +74,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/object-capabilities/TextContainerBehavior.ts b/GDJS/Runtime/object-capabilities/TextContainerBehavior.ts index 94700da71..33f0579ed 100644 --- a/GDJS/Runtime/object-capabilities/TextContainerBehavior.ts +++ b/GDJS/Runtime/object-capabilities/TextContainerBehavior.ts @@ -41,7 +41,7 @@ namespace gdjs { return false; } - updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { + override applyBehaviorOverriding(behaviorData): boolean { // Nothing to update. return true; } diff --git a/GDJS/Runtime/runtimebehavior.ts b/GDJS/Runtime/runtimebehavior.ts index 787a6a0b4..09a2db2f5 100644 --- a/GDJS/Runtime/runtimebehavior.ts +++ b/GDJS/Runtime/runtimebehavior.ts @@ -79,6 +79,22 @@ namespace gdjs { oldBehaviorData: BehaviorData, newBehaviorData: BehaviorData ): boolean { + const diffBehaviorData = { + name: newBehaviorData.name, + type: newBehaviorData.type, + }; + for (const key in diffBehaviorData) { + const oldValue = oldBehaviorData[key]; + const newValue = newBehaviorData[key]; + if (newValue !== oldValue) { + diffBehaviorData[key] = newValue; + } + } + // If not redefined, mark by default the hot-reload as failed. + return this.applyBehaviorOverriding(diffBehaviorData); + } + + applyBehaviorOverriding(behaviorOverriding: BehaviorData): boolean { // If not redefined, mark by default the hot-reload as failed. return false; } diff --git a/GDJS/Runtime/runtimeobject.ts b/GDJS/Runtime/runtimeobject.ts index 601883d46..555988326 100644 --- a/GDJS/Runtime/runtimeobject.ts +++ b/GDJS/Runtime/runtimeobject.ts @@ -240,7 +240,8 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - objectData: ObjectData + objectData: ObjectData, + instanceData: InstanceData | undefined ) { this.name = objectData.name || ''; this.type = objectData.type || ''; @@ -275,6 +276,15 @@ namespace gdjs { } this._behaviorsTable.put(autoData.name, behavior); } + if (instanceData && instanceData.behaviorOverridings) { + for (const behaviorOverriding of instanceData.behaviorOverridings) { + const behavior = this.getBehavior(behaviorOverriding.name); + if (!behavior) { + continue; + } + behavior.applyBehaviorOverriding(behaviorOverriding); + } + } this._timers = new Hashtable(); } diff --git a/GDJS/Runtime/spriteruntimeobject.ts b/GDJS/Runtime/spriteruntimeobject.ts index d865aad2a..e5e5c0ce6 100644 --- a/GDJS/Runtime/spriteruntimeobject.ts +++ b/GDJS/Runtime/spriteruntimeobject.ts @@ -73,9 +73,10 @@ namespace gdjs { */ constructor( instanceContainer: gdjs.RuntimeInstanceContainer, - spriteObjectData: ObjectData & SpriteObjectDataType + spriteObjectData: ObjectData & SpriteObjectDataType, + instanceData?: InstanceData ) { - super(instanceContainer, spriteObjectData); + super(instanceContainer, spriteObjectData, instanceData); this._updateIfNotVisible = !!spriteObjectData.updateIfNotVisible; this._preScale = spriteObjectData.preScale || 1; this._renderer = new gdjs.SpriteRuntimeObjectRenderer( diff --git a/GDJS/Runtime/types/project-data.d.ts b/GDJS/Runtime/types/project-data.d.ts index 6eb8238c8..8280b7f83 100644 --- a/GDJS/Runtime/types/project-data.d.ts +++ b/GDJS/Runtime/types/project-data.d.ts @@ -450,6 +450,7 @@ declare interface InstanceData extends InstancePersistentUuidData { numberProperties: InstanceNumberProperty[]; stringProperties: InstanceStringProperty[]; initialVariables: RootVariableData[]; + behaviorOverridings?: Array; } declare interface InstanceNumberProperty { diff --git a/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js b/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js index 9d5b6a23a..b60c36c7a 100644 --- a/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js +++ b/GDJS/tests/tests-utils/init.pixiruntimegamewithassets.js @@ -4,14 +4,18 @@ * Create and return a game with a few assets loaded, to be used in tests * needing real images. * @internal + * @param props {?{customObjectInstances?: Array}} * @returns {Promise} A promise resolving with the game with loaded assets. */ -gdjs.getPixiRuntimeGameWithAssets = () => { - if (gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise) { +gdjs.getPixiRuntimeGameWithAssets = (props = null) => { + if ( + gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise && + !props + ) { return gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise; } - var runtimeGame = new gdjs.RuntimeGame({ + const runtimeGame = new gdjs.RuntimeGame({ variables: [], properties: { adaptGameResolutionAtRuntime: true, @@ -77,19 +81,19 @@ gdjs.getPixiRuntimeGameWithAssets = () => { title: '', variables: [], usedResources: [], - uiSettings: { - grid: false, - gridType: 'rectangular', - gridWidth: 10, - gridHeight: 10, - gridDepth: 10, - gridOffsetX: 0, - gridOffsetY: 0, - gridOffsetZ: 0, - gridColor: 0, - gridAlpha: 1, - snap: false, - } + uiSettings: { + grid: false, + gridType: 'rectangular', + gridWidth: 10, + gridHeight: 10, + gridDepth: 10, + gridOffsetX: 0, + gridOffsetY: 0, + gridOffsetZ: 0, + gridColor: 0, + gridAlpha: 1, + snap: false, + }, }, ], externalLayouts: [], @@ -207,7 +211,7 @@ gdjs.getPixiRuntimeGameWithAssets = () => { ], }, ], - instances: [ + instances: (props && props.customObjectInstances) || [ { angle: 0, customSize: true, @@ -242,20 +246,23 @@ gdjs.getPixiRuntimeGameWithAssets = () => { ], }); - gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise = - new Promise((resolve) => { - runtimeGame.loadAllAssets( - () => { - console.info('Done loading assets for test game'); + const pixiRuntimeGameWithAssetsPromise = new Promise((resolve) => { + runtimeGame.loadAllAssets( + () => { + console.info('Done loading assets for test game'); - resolve(runtimeGame); - }, - () => { - /* Ignore progress */ - } - ); - }); - return gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise; + resolve(runtimeGame); + }, + () => { + /* Ignore progress */ + } + ); + }); + if (!props) { + gdjs.getPixiRuntimeGameWithAssets._pixiRuntimeGameWithAssetsPromise = + pixiRuntimeGameWithAssetsPromise; + } + return pixiRuntimeGameWithAssetsPromise; }; /** diff --git a/GDJS/tests/tests/CustomRuntimeObject.js b/GDJS/tests/tests/CustomRuntimeObject.js index 59f85a5e1..ca5b730ad 100644 --- a/GDJS/tests/tests/CustomRuntimeObject.js +++ b/GDJS/tests/tests/CustomRuntimeObject.js @@ -61,7 +61,7 @@ describe('gdjs.CustomRuntimeObject', function () { }, ], }, - }, + } }); instanceContainer.addObject(customObject); return customObject; diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 18df4cc34..fefb753b2 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -805,7 +805,9 @@ interface Behavior { [Value] MapStringPropertyDescriptor GetProperties(); boolean UpdateProperty([Const] DOMString name, [Const] DOMString value); + void RemoveProperty([Const] DOMString name); void InitializeContent(); + boolean HasPropertyValue([Const] DOMString name); void SerializeTo([Ref] SerializerElement element); void UnserializeFrom([Const, Ref] SerializerElement element); @@ -1055,7 +1057,7 @@ interface ExternalLayout { [Ref] EditorSettings GetAssociatedEditorSettings(); void SerializeTo([Ref] SerializerElement element); - void UnserializeFrom([Const, Ref] SerializerElement element); + void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element); }; interface Effect { @@ -1492,8 +1494,17 @@ interface InitialInstance { [Ref] VariablesContainer GetVariables(); + boolean HasAnyOverriddenProperty([Const, Ref] gdObject obj); + boolean HasAnyOverriddenPropertyForBehavior([Const, Ref] Behavior behavior); + + boolean HasBehaviorOverridingNamed([Const] DOMString name); + Behavior AddNewBehaviorOverriding([Ref] Project project, [Const] DOMString type, [Const] DOMString name); + [Ref] Behavior GetBehaviorOverriding([Const] DOMString name); + void RemoveBehaviorOverriding([Const] DOMString name); + boolean RenameBehaviorOverriding([Const] DOMString oldName, [Const] DOMString name); + void SerializeTo([Ref] SerializerElement element); - void UnserializeFrom([Const, Ref] SerializerElement element); + void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element); }; interface InitialInstancesContainer { @@ -1518,7 +1529,7 @@ interface InitialInstancesContainer { [Ref] InitialInstance InsertInitialInstance([Const, Ref] InitialInstance inst); void SerializeTo([Ref] SerializerElement element); - void UnserializeFrom([Const, Ref] SerializerElement element); + void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element); }; interface HighestZOrderFinder { diff --git a/GDevelop.js/__tests__/Core.js b/GDevelop.js/__tests__/Core.js index 07ea513fe..0a70aea01 100644 --- a/GDevelop.js/__tests__/Core.js +++ b/GDevelop.js/__tests__/Core.js @@ -809,7 +809,7 @@ describe('libGD.js', function () { let initialInstance2 = layout .getInitialInstances() .insertNewInitialInstance(); - initialInstance2.unserializeFrom(element); + initialInstance2.unserializeFrom(project, element); expect(initialInstance2.getObjectName()).toBe('MySpriteObject'); expect(initialInstance2.getX()).toBe(150); expect(initialInstance2.getY()).toBe(140); @@ -863,7 +863,7 @@ describe('libGD.js', function () { const migratedInstance = layout .getInitialInstances() .insertNewInitialInstance(); - migratedInstance.unserializeFrom(element); + migratedInstance.unserializeFrom(project, element); element.delete(); expect(migratedInstance.getX()).toBe(-100); @@ -902,7 +902,7 @@ describe('libGD.js', function () { const instanceWithJustDepth = layout .getInitialInstances() .insertNewInitialInstance(); - instanceWithJustDepth.unserializeFrom(element); + instanceWithJustDepth.unserializeFrom(project, element); element.delete(); expect(instanceWithJustDepth.getX()).toBe(-100); @@ -950,7 +950,7 @@ describe('libGD.js', function () { const initialInstance2 = layout .getInitialInstances() .insertNewInitialInstance(); - initialInstance2.unserializeFrom(element); + initialInstance2.unserializeFrom(project, element); expect(initialInstance2.getX()).toBe(40); expect(initialInstance2.getY()).toBe(41); expect(initialInstance2.getZ()).toBe(42); @@ -975,7 +975,7 @@ describe('libGD.js', function () { // The UUID must persist across serializations. const initialInstance2 = new gd.InitialInstance(); - initialInstance2.unserializeFrom(element); + initialInstance2.unserializeFrom(project, element); let element2 = new gd.SerializerElement(); initialInstance.serializeTo(element2); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 3be5da840..6feaa931a 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -692,7 +692,9 @@ export class Behavior extends EmscriptenObject { getTypeName(): string; getProperties(): MapStringPropertyDescriptor; updateProperty(name: string, value: string): boolean; + removeProperty(name: string): void; initializeContent(): void; + hasPropertyValue(name: string): boolean; serializeTo(element: SerializerElement): void; unserializeFrom(element: SerializerElement): void; isFolded(): boolean; @@ -866,7 +868,7 @@ export class ExternalLayout extends EmscriptenObject { getInitialInstances(): InitialInstancesContainer; getAssociatedEditorSettings(): EditorSettings; serializeTo(element: SerializerElement): void; - unserializeFrom(element: SerializerElement): void; + unserializeFrom(project: Project, element: SerializerElement): void; } export class Effect extends EmscriptenObject { @@ -1233,8 +1235,15 @@ export class InitialInstance extends EmscriptenObject { setRawDoubleProperty(name: string, value: number): void; setRawStringProperty(name: string, value: string): void; getVariables(): VariablesContainer; + hasAnyOverriddenProperty(obj: gdObject): boolean; + hasAnyOverriddenPropertyForBehavior(behavior: Behavior): boolean; + hasBehaviorOverridingNamed(name: string): boolean; + addNewBehaviorOverriding(project: Project, type: string, name: string): Behavior; + getBehaviorOverriding(name: string): Behavior; + removeBehaviorOverriding(name: string): void; + renameBehaviorOverriding(oldName: string, name: string): boolean; serializeTo(element: SerializerElement): void; - unserializeFrom(element: SerializerElement): void; + unserializeFrom(project: Project, element: SerializerElement): void; } export class InitialInstancesContainer extends EmscriptenObject { @@ -1255,7 +1264,7 @@ export class InitialInstancesContainer extends EmscriptenObject { insertNewInitialInstance(): InitialInstance; insertInitialInstance(inst: InitialInstance): InitialInstance; serializeTo(element: SerializerElement): void; - unserializeFrom(element: SerializerElement): void; + unserializeFrom(project: Project, element: SerializerElement): void; } export class HighestZOrderFinder extends EmscriptenObject { diff --git a/GDevelop.js/types/gdbehavior.js b/GDevelop.js/types/gdbehavior.js index 083a1edc9..7a14a3b64 100644 --- a/GDevelop.js/types/gdbehavior.js +++ b/GDevelop.js/types/gdbehavior.js @@ -6,7 +6,9 @@ declare class gdBehavior { getTypeName(): string; getProperties(): gdMapStringPropertyDescriptor; updateProperty(name: string, value: string): boolean; + removeProperty(name: string): void; initializeContent(): void; + hasPropertyValue(name: string): boolean; serializeTo(element: gdSerializerElement): void; unserializeFrom(element: gdSerializerElement): void; isFolded(): boolean; diff --git a/GDevelop.js/types/gdexternallayout.js b/GDevelop.js/types/gdexternallayout.js index dde8d681a..551f9e133 100644 --- a/GDevelop.js/types/gdexternallayout.js +++ b/GDevelop.js/types/gdexternallayout.js @@ -8,7 +8,7 @@ declare class gdExternalLayout { getInitialInstances(): gdInitialInstancesContainer; getAssociatedEditorSettings(): gdEditorSettings; serializeTo(element: gdSerializerElement): void; - unserializeFrom(element: gdSerializerElement): void; + unserializeFrom(project: gdProject, element: gdSerializerElement): void; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdinitialinstance.js b/GDevelop.js/types/gdinitialinstance.js index bfd726b91..9d2dd29d3 100644 --- a/GDevelop.js/types/gdinitialinstance.js +++ b/GDevelop.js/types/gdinitialinstance.js @@ -58,8 +58,15 @@ declare class gdInitialInstance { setRawDoubleProperty(name: string, value: number): void; setRawStringProperty(name: string, value: string): void; getVariables(): gdVariablesContainer; + hasAnyOverriddenProperty(obj: gdObject): boolean; + hasAnyOverriddenPropertyForBehavior(behavior: gdBehavior): boolean; + hasBehaviorOverridingNamed(name: string): boolean; + addNewBehaviorOverriding(project: gdProject, type: string, name: string): gdBehavior; + getBehaviorOverriding(name: string): gdBehavior; + removeBehaviorOverriding(name: string): void; + renameBehaviorOverriding(oldName: string, name: string): boolean; serializeTo(element: gdSerializerElement): void; - unserializeFrom(element: gdSerializerElement): void; + unserializeFrom(project: gdProject, element: gdSerializerElement): void; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdinitialinstancescontainer.js b/GDevelop.js/types/gdinitialinstancescontainer.js index d866a26cc..a967c7fe7 100644 --- a/GDevelop.js/types/gdinitialinstancescontainer.js +++ b/GDevelop.js/types/gdinitialinstancescontainer.js @@ -17,7 +17,7 @@ declare class gdInitialInstancesContainer { insertNewInitialInstance(): gdInitialInstance; insertInitialInstance(inst: gdInitialInstance): gdInitialInstance; serializeTo(element: gdSerializerElement): void; - unserializeFrom(element: gdSerializerElement): void; + unserializeFrom(project: gdProject, element: gdSerializerElement): void; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js b/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js index 6c19f045a..3ab99998a 100644 --- a/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js +++ b/newIDE/app/src/BehaviorsEditor/Editors/BehaviorPropertiesEditor.js @@ -29,7 +29,11 @@ const BehaviorPropertiesEditor = ({ propertiesMapToSchema({ properties: behavior.getProperties(), defaultValueProperties: behaviorMetadata.getProperties(), - getProperties: instance => instance.getProperties(), + getPropertyValue: (instance, name) => + instance + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (instance, name, value) => { instance.updateProperty(name, value); }, diff --git a/newIDE/app/src/BehaviorsEditor/index.js b/newIDE/app/src/BehaviorsEditor/index.js index 844526832..3387ecb76 100644 --- a/newIDE/app/src/BehaviorsEditor/index.js +++ b/newIDE/app/src/BehaviorsEditor/index.js @@ -395,6 +395,7 @@ export const useManageObjectBehaviors = ({ // Renaming a behavior is something that is really rare anyway! :) if (object.hasBehaviorNamed(newName)) return; + // TODO Add a refactor operation to update the behavior name in overridings of object instances object.renameBehavior(behavior.getName(), newName); onUpdate(); if (onBehaviorsUpdated) onBehaviorsUpdated(); @@ -419,6 +420,7 @@ export const useManageObjectBehaviors = ({ const answer = Window.showConfirmDialog(message); if (answer) { + // TODO Add a refactor operation to remove the behavior overridings in object instances object.removeBehavior(behaviorName); dependentBehaviors.forEach(name => object.removeBehavior(name)); if (onSizeUpdated) onSizeUpdated(); diff --git a/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorByVisibility.js b/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorByVisibility.js index 371c1faa9..ea89ca345 100644 --- a/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorByVisibility.js +++ b/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorByVisibility.js @@ -162,7 +162,7 @@ export const CompactPropertiesEditorByVisibility = ({ instances: Instances, preventWrap?: boolean, removeSpacers?: boolean, - customizeBasicSchema?: Schema => Schema, + customizeBasicSchema?: (Schema => Schema) | null, placeholder: React.Node, onRefreshAllFields: () => void, diff --git a/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorRowField.js b/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorRowField.js index cf4a96970..2a99856c4 100644 --- a/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorRowField.js +++ b/newIDE/app/src/CompactPropertiesEditor/CompactPropertiesEditorRowField.js @@ -15,13 +15,13 @@ const styles = { textOverflow: 'ellipsis', lineHeight: '17px', maxHeight: 34, // 2 * lineHeight to limit to 2 lines. - opacity: 0.7, }, }; type Props = {| label: string, markdownDescription?: ?string, field: React.Node, + labelColor?: 'primary' | 'secondary', |}; const CompactPropertiesEditorRowField = (props: Props) => { @@ -52,7 +52,11 @@ const CompactPropertiesEditorRowField = (props: Props) => { }, }} > - + {props.label} diff --git a/newIDE/app/src/CompactPropertiesEditor/index.js b/newIDE/app/src/CompactPropertiesEditor/index.js index 109b39f7b..dc309d70f 100644 --- a/newIDE/app/src/CompactPropertiesEditor/index.js +++ b/newIDE/app/src/CompactPropertiesEditor/index.js @@ -146,6 +146,23 @@ const getFieldEndAdornmentIcon = ({ return null; }; +const isFieldHighlighted = ({ + instances, + field, +}: {| + instances: Instances, + field: ValueField, +|}): any => { + if (!instances[0]) { + console.warn( + 'isFieldHighlighted was called with an empty list of instances (or containing undefined). This is a bug that should be fixed.' + ); + return false; + } + + return field.isHighlighted ? field.isHighlighted(instances[0]) : false; +}; + const getFieldLabel = ({ instances, field, @@ -247,6 +264,9 @@ const CompactPropertiesEditor = ({ }} disabled={getDisabled({ instances, field })} fullWidth + labelColor={ + isFieldHighlighted({ instances, field }) ? 'primary' : 'secondary' + } /> ); } else if (field.valueType === 'number') { @@ -306,6 +326,11 @@ const CompactPropertiesEditor = ({ {...otherCommonProps} /> } + labelColor={ + isFieldHighlighted({ instances, field }) + ? 'primary' + : 'secondary' + } /> ); } @@ -332,6 +357,9 @@ const CompactPropertiesEditor = ({ }} /> } + labelColor={ + isFieldHighlighted({ instances, field }) ? 'primary' : 'secondary' + } /> ); } else if (field.valueType === 'enumIcon') { @@ -372,6 +400,9 @@ const CompactPropertiesEditor = ({ value={getFieldValue({ instances, field })} label={getFieldLabel({ instances, field })} markdownDescription={getFieldDescription(field)} + labelColor={ + isFieldHighlighted({ instances, field }) ? 'primary' : 'secondary' + } /> ); } else { @@ -426,12 +457,17 @@ const CompactPropertiesEditor = ({ label={getFieldLabel({ instances, field })} markdownDescription={getFieldDescription(field)} field={} + labelColor={ + isFieldHighlighted({ instances, field }) + ? 'primary' + : 'secondary' + } /> ); } } }, - [instances, onFieldChanged, getFieldDescription] + [instances, getFieldDescription, onFieldChanged] ); const renderSelectField = React.useCallback( @@ -457,7 +493,7 @@ const CompactPropertiesEditor = ({ compactSelectField = ( { instances.forEach(i => setValue(i, parseFloat(newValue) || 0)); @@ -466,7 +502,7 @@ const CompactPropertiesEditor = ({ hasImpactOnAllOtherFields: field.hasImpactOnAllOtherFields, }); }} - disabled={field.disabled} + disabled={getDisabled({ instances, field })} > {children} @@ -507,10 +543,13 @@ const CompactPropertiesEditor = ({ label={getFieldLabel({ instances, field })} markdownDescription={getFieldDescription(field)} field={compactSelectField} + labelColor={ + isFieldHighlighted({ instances, field }) ? 'primary' : 'secondary' + } /> ); }, - [instances, onFieldChanged, getFieldDescription] + [instances, getFieldDescription, onFieldChanged] ); const renderButton = React.useCallback( @@ -619,6 +658,9 @@ const CompactPropertiesEditor = ({ }} /> } + labelColor={ + isFieldHighlighted({ instances, field }) ? 'primary' : 'secondary' + } /> ); }; @@ -652,6 +694,9 @@ const CompactPropertiesEditor = ({ }} /> } + labelColor={ + isFieldHighlighted({ instances, field }) ? 'primary' : 'secondary' + } /> ); }; diff --git a/newIDE/app/src/EffectsList/EnumerateEffects.js b/newIDE/app/src/EffectsList/EnumerateEffects.js index 10c96fc68..7dfc62177 100644 --- a/newIDE/app/src/EffectsList/EnumerateEffects.js +++ b/newIDE/app/src/EffectsList/EnumerateEffects.js @@ -40,7 +40,6 @@ export const enumerateEffectsMetadata = ( // Convert the effect type properties to a PropertiesEditor Schema. const parametersSchema: Schema = effectPropertiesMapToSchema({ defaultValueProperties: effectMetadata.getProperties(), - getProperties: instance => effectMetadata.getProperties(), }); return { extension, diff --git a/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js b/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js index 0065a547d..66a8adf14 100644 --- a/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js +++ b/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js @@ -31,9 +31,28 @@ import TileSetVisualizer, { type TileMapTileSelection, } from '../TileSetVisualizer'; import Add from '../../UI/CustomSvgIcons/Add'; +import { + TopLevelCollapsibleSection, + CollapsibleSubPanel, + type TitleBarButton, +} from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { ColumnStackLayout } from '../../UI/Layout'; +import Link from '../../UI/Link'; +import { CompactBehaviorPropertiesEditor } from '../../ObjectEditor/CompactObjectPropertiesEditor/CompactBehaviorPropertiesEditor'; +import { IconContainer } from '../../UI/IconContainer'; +import { getHelpLink } from '../../Utils/HelpLink'; +import Window from '../../Utils/Window'; +import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource'; +import EmptyMessage from '../../UI/EmptyMessage'; const gd: libGDevelop = global.gd; +const notOverridableBehaviorTypes = [ + 'Physics2::Physics2Behavior', + 'Physics3D::Physics3DBehavior', + 'PhysicsBehavior::PhysicsBehavior', +]; + export const styles = { icon: { fontSize: 18, @@ -41,14 +60,56 @@ export const styles = { scrollView: { paddingTop: marginsSize }, }; +const behaviorsHelpLink = getHelpLink('/behaviors'); + const noRefreshOfAllFields = () => { console.warn( "An instance tried to refresh all fields, but the editor doesn't support it." ); }; +export const StatefulCollapsibleSubPanel = ({ + renderContent, + isInitiallyFolded, + onToggleFolded, + title, + titleIcon, + titleBarButtons, +}: {| + renderContent: () => React.Node, + isInitiallyFolded: boolean, + onToggleFolded?: () => void, + titleIcon?: ?React.Node, + title: string, + titleBarButtons?: Array, +|}) => { + const [isFolded, setIsFolded] = React.useState(isInitiallyFolded); + + const toggleFolded = React.useCallback( + () => { + setIsFolded(isFolded => !isFolded); + if (onToggleFolded) { + onToggleFolded(); + } + }, + [onToggleFolded] + ); + + return ( + + ); +}; + type Props = {| project: gdProject, + resourceManagementProps: ResourceManagementProps, layout?: ?gdLayout, objectsContainer: gdObjectsContainer, globalObjectsContainer: gdObjectsContainer | null, @@ -65,12 +126,14 @@ type Props = {| tileMapTileSelection: ?TileMapTileSelection, onSelectTileMapTile: (?TileMapTileSelection) => void, isVariableListLocked: boolean, + canOverrideBehaviorProperties: boolean, |}; export const CompactInstancePropertiesEditor = ({ instances, i18n, project, + resourceManagementProps, layout, objectsContainer, globalObjectsContainer, @@ -85,12 +148,13 @@ export const CompactInstancePropertiesEditor = ({ tileMapTileSelection, onSelectTileMapTile, isVariableListLocked, + canOverrideBehaviorProperties, }: Props) => { const forceUpdate = useForceUpdate(); + const instance = instances[0]; const variablesListRef = React.useRef(null); const scrollViewRef = React.useRef(null); - const instance = instances[0]; /** * TODO: multiple instances support for variables list. Expected behavior should be: * - if instances of different objects, do not show @@ -105,12 +169,18 @@ export const CompactInstancePropertiesEditor = ({ } }, []); - const { object, instanceSchema } = React.useMemo<{| + const { object, instanceSchema, allVisibleBehaviors } = React.useMemo<{| object?: gdObject, instanceSchema?: Schema, + allVisibleBehaviors: Array, |}>( () => { - if (!instance) return { object: undefined, instanceSchema: undefined }; + if (!instance) + return { + object: undefined, + instanceSchema: undefined, + allVisibleBehaviors: [], + }; const associatedObjectName = instance.getObjectName(); const object = getObjectByName( @@ -122,7 +192,21 @@ export const CompactInstancePropertiesEditor = ({ globalObjectsContainer || objectsContainer, objectsContainer ); - if (!object) return { object: undefined, instanceSchema: undefined }; + if (!object) + return { + object: undefined, + instanceSchema: undefined, + allVisibleBehaviors: [], + }; + + const allVisibleBehaviors = object + .getAllBehaviorNames() + .toJSArray() + .map(behaviorName => object.getBehavior(behaviorName)) + .filter(behavior => !behavior.isDefaultBehavior()) + // We don't keep the behaviors directly because they may be destroyed + // if the object is rebuilt from a serialization. + .map(behavior => behavior.getName()); const objectMetadata = gd.MetadataProvider.getObjectMetadata( project.getCurrentPlatform(), @@ -142,11 +226,14 @@ export const CompactInstancePropertiesEditor = ({ properties, // We can't access default values for instance custom properties. defaultValueProperties: null, - getProperties: (instance: gdInitialInstance) => - instance.getCustomProperties( - globalObjectsContainer || objectsContainer, - objectsContainer - ), + getPropertyValue: (instance: gdInitialInstance, name: string) => + instance + .getCustomProperties( + globalObjectsContainer || objectsContainer, + objectsContainer + ) + .get(name) + .getValue(), onUpdateProperty: (instance: gdInitialInstance, name, value) => instance.updateCustomProperty( name, @@ -174,6 +261,7 @@ export const CompactInstancePropertiesEditor = ({ return { object, instanceSchema, + allVisibleBehaviors, }; }, [ @@ -189,6 +277,10 @@ export const CompactInstancePropertiesEditor = ({ ] ); + const [isBehaviorsFolded, setIsBehaviorsFolded] = React.useState( + object ? !instance.hasAnyOverriddenProperty(object) : true + ); + const shouldDisplayTileSetVisualizer = !!object && object.getType() === 'TileMap::SimpleTileMap'; @@ -263,6 +355,113 @@ export const CompactInstancePropertiesEditor = ({ )} + {object && canOverrideBehaviorProperties ? ( + Behaviors} + isFolded={isBehaviorsFolded} + toggleFolded={() => setIsBehaviorsFolded(!isBehaviorsFolded)} + renderContent={() => ( + + {!allVisibleBehaviors.length && ( + + + There are no{' '} + + Window.openExternalURL(behaviorsHelpLink) + } + > + behaviors + {' '} + on this object instance. + + + )} + {allVisibleBehaviors.map(behaviorName => { + const behavior = object.getBehavior(behaviorName); + const behaviorTypeName = behavior.getTypeName(); + const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata( + gd.JsPlatform.get(), + behaviorTypeName + ); + const behaviorOverriding = instance.hasBehaviorOverridingNamed( + behaviorName + ) + ? instance.getBehaviorOverriding(behaviorName) + : null; + + const iconUrl = behaviorMetadata.getIconFilename(); + + return ( + ( + + + + This behavior can't be setup per instance. + + + + ) + : () => ( + { + if ( + instance.hasBehaviorOverridingNamed( + behaviorName + ) && + !instance.hasAnyOverriddenPropertyForBehavior( + behavior + ) + ) { + instance.removeBehaviorOverriding( + behaviorName + ); + // Update the view to stop using + // the removed behavior overriding. + forceUpdate(); + } + }} + resourceManagementProps={ + resourceManagementProps + } + /> + ) + } + isInitiallyFolded={ + !instance.hasAnyOverriddenPropertyForBehavior( + behavior + ) + } + titleIcon={ + iconUrl ? ( + + ) : null + } + title={behavior.getName()} + /> + ); + })} + + )} + /> + ) : null} {object && shouldDisplayVariablesList ? ( <> diff --git a/newIDE/app/src/InstancesEditor/InstancesAdder.js b/newIDE/app/src/InstancesEditor/InstancesAdder.js index 63f0fbac0..366548476 100644 --- a/newIDE/app/src/InstancesEditor/InstancesAdder.js +++ b/newIDE/app/src/InstancesEditor/InstancesAdder.js @@ -6,12 +6,14 @@ import { type InstancesEditorSettings } from './InstancesEditorSettings'; const gd: libGDevelop = global.gd; export const addSerializedInstances = ({ + project, instancesContainer, copyReferential, serializedInstances, addInstancesInTheForeground = false, doesObjectExistInContext, }: {| + project: gdProject, instancesContainer: gdInitialInstancesContainer, copyReferential: [number, number], serializedInstances: Array, @@ -29,7 +31,12 @@ export const addSerializedInstances = ({ const newInstances = serializedInstances .map(serializedInstance => { const instance = new gd.InitialInstance(); - unserializeFromJSObject(instance, serializedInstance); + unserializeFromJSObject( + instance, + serializedInstance, + 'unserializeFrom', + project + ); if (!doesObjectExistInContext(instance.getObjectName())) return null; instance.setX(instance.getX() - copyReferential[0]); instance.setY(instance.getY() - copyReferential[1]); @@ -65,6 +72,7 @@ export const addSerializedInstances = ({ }; type Props = {| + project: gdProject, instances: gdInitialInstancesContainer, instancesEditorSettings: InstancesEditorSettings, |}; @@ -74,12 +82,14 @@ type Props = {| * which are real instances but can be deleted as long as they are not "committed". */ export default class InstancesAdder { + _project: gdProject; _instances: gdInitialInstancesContainer; _temporaryInstances: Array; _instancesEditorSettings: InstancesEditorSettings; _zOrderFinder = new gd.HighestZOrderFinder(); - constructor({ instances, instancesEditorSettings }: Props) { + constructor({ project, instances, instancesEditorSettings }: Props) { + this._project = project; this._instances = instances; this._instancesEditorSettings = instancesEditorSettings; this._temporaryInstances = []; @@ -103,6 +113,7 @@ export default class InstancesAdder { doesObjectExistInContext: string => boolean, |}): Array => { const instances = addSerializedInstances({ + project: this._project, instancesContainer: this._instances, copyReferential, serializedInstances, diff --git a/newIDE/app/src/InstancesEditor/index.js b/newIDE/app/src/InstancesEditor/index.js index cd621ea84..53b49bba0 100644 --- a/newIDE/app/src/InstancesEditor/index.js +++ b/newIDE/app/src/InstancesEditor/index.js @@ -431,6 +431,7 @@ export default class InstancesEditor extends Component { }); this._instancesAdder = new InstancesAdder({ + project: this.props.project, instances: this.props.initialInstances, instancesEditorSettings: this.props.instancesEditorSettings, }); diff --git a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/ChildObjectPropertiesEditor.js b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/ChildObjectPropertiesEditor.js index 708ebb994..e1070a9b6 100644 --- a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/ChildObjectPropertiesEditor.js +++ b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/ChildObjectPropertiesEditor.js @@ -59,8 +59,11 @@ export const ChildObjectPropertiesEditor = ({ return propertiesMapToSchema({ properties: childObjectConfigurationAsGd.getProperties(), defaultValueProperties: null, - getProperties: ({ object, objectConfiguration }) => - objectConfiguration.getProperties(), + getPropertyValue: ({ object, objectConfiguration }, name) => + objectConfiguration + .getProperties() + .get(name) + .getValue(), onUpdateProperty: ({ object, objectConfiguration }, name, value) => objectConfiguration.updateProperty(name, value), visibility: 'All', diff --git a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/CompactBehaviorPropertiesEditor.js b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/CompactBehaviorPropertiesEditor.js index 0cf7183e2..6025fcc62 100644 --- a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/CompactBehaviorPropertiesEditor.js +++ b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/CompactBehaviorPropertiesEditor.js @@ -59,6 +59,8 @@ export const CompactBehaviorPropertiesEditor = ({ behaviorMetadata, behavior, object, + behaviorOverriding, + initialInstance, onOpenFullEditor, onBehaviorUpdated, resourceManagementProps, @@ -67,7 +69,9 @@ export const CompactBehaviorPropertiesEditor = ({ behaviorMetadata: gdBehaviorMetadata, behavior: gdBehavior, object: gdObject, - onOpenFullEditor: () => void, + behaviorOverriding: gdBehavior | null, + initialInstance: gdInitialInstance | null, + onOpenFullEditor?: () => void, onBehaviorUpdated: () => void, resourceManagementProps: ResourceManagementProps, |}) => { @@ -80,10 +84,67 @@ export const CompactBehaviorPropertiesEditor = ({ if (schemaRecomputeTrigger) { // schemaRecomputeTrigger allows to invalidate the schema when required. } + if (initialInstance) { + const behaviorProperties = behavior.getProperties(); + return propertiesMapToSchema({ + properties: behaviorProperties, + defaultValueProperties: behaviorProperties, + getPropertyValue: (instance, propertyName) => { + const behaviorName = behavior.getName(); + if ( + initialInstance.hasBehaviorOverridingNamed(behaviorName) && + initialInstance + .getBehaviorOverriding(behaviorName) + .hasPropertyValue(propertyName) + ) { + const behaviorOverriding = initialInstance.getBehaviorOverriding( + behaviorName + ); + return behaviorOverriding + .getProperties() + .get(propertyName) + .getValue(); + } + return behavior + .getProperties() + .get(propertyName) + .getValue(); + }, + onUpdateProperty: (instance, name, value) => { + const behaviorName = behavior.getName(); + const behaviorOverriding = initialInstance.hasBehaviorOverridingNamed( + behaviorName + ) + ? initialInstance.getBehaviorOverriding(behaviorName) + : initialInstance.addNewBehaviorOverriding( + project, + behavior.getTypeName(), + behaviorName + ); + const behaviorProperties = behavior.getProperties(); + const inheritedValue = behaviorProperties.has(name) + ? behaviorProperties.get(name).getValue() + : null; + if (inheritedValue === value) { + behaviorOverriding.removeProperty(name); + } else { + behaviorOverriding.updateProperty(name, value); + } + }, + object, + visibility: 'All', + showcaseNonDefaultValues: true, + }); + } + const behaviorMetadataProperties = behaviorMetadata.getProperties(); return propertiesMapToSchema({ - properties: behavior.getProperties(), - defaultValueProperties: behaviorMetadata.getProperties(), - getProperties: instance => instance.getProperties(), + properties: behaviorMetadataProperties, + defaultValueProperties: behaviorMetadataProperties, + getPropertyValue: (instance, name) => + instance + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (instance, name, value) => { instance.updateProperty(name, value); }, @@ -91,7 +152,14 @@ export const CompactBehaviorPropertiesEditor = ({ visibility: 'All', }); }, - [schemaRecomputeTrigger, behavior, behaviorMetadata, object] + [ + schemaRecomputeTrigger, + initialInstance, + behavior, + behaviorMetadata, + object, + project, + ] ); return ( @@ -104,13 +172,16 @@ export const CompactBehaviorPropertiesEditor = ({ onInstancesModified={onBehaviorUpdated} resourceManagementProps={resourceManagementProps} placeholder={Nothing to configure for this behavior.} - customizeBasicSchema={schema => - getSchemaWithOpenFullEditorButton({ - schema, - fullEditorLabel, - behavior, - onOpenFullEditor, - }) + customizeBasicSchema={ + onOpenFullEditor + ? schema => + getSchemaWithOpenFullEditorButton({ + schema, + fullEditorLabel, + behavior, + onOpenFullEditor, + }) + : null } onRefreshAllFields={forceRecomputeSchema} /> diff --git a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js index eeb58ca1e..8ade9e427 100644 --- a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js +++ b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js @@ -89,14 +89,14 @@ const objectVariablesHelpLink = getHelpLink( '/all-features/variables/object-variables' ); -type TitleBarButton = {| +export type TitleBarButton = {| id: string, icon: any, label?: MessageDescriptor, onClick?: () => void, |}; -const CollapsibleSubPanel = ({ +export const CollapsibleSubPanel = ({ renderContent, isFolded, toggleFolded, @@ -157,7 +157,7 @@ const CollapsibleSubPanel = ({ ); -const TopLevelCollapsibleSection = ({ +export const TopLevelCollapsibleSection = ({ title, isFolded, toggleFolded, @@ -173,7 +173,7 @@ const TopLevelCollapsibleSection = ({ renderContent: () => React.Node, renderContentAsHiddenWhenFolded?: boolean, noContentMargin?: boolean, - onOpenFullEditor: () => void, + onOpenFullEditor?: () => void, onAdd?: (() => void) | null, |}) => ( <> @@ -193,9 +193,11 @@ const TopLevelCollapsibleSection = ({ - - - + {onOpenFullEditor && ( + + + + )} {onAdd && ( @@ -492,8 +494,11 @@ export const CompactObjectPropertiesEditor = ({ ? customObjectEventsBasedObject.getPropertyDescriptors() : // We can't access default values for built-in objects. null, - getProperties: ({ objectConfiguration }) => - objectConfiguration.getProperties(), + getPropertyValue: ({ objectConfiguration }, name) => + objectConfiguration + .getProperties() + .get(name) + .getValue(), onUpdateProperty: ({ objectConfiguration }, name, value) => { objectConfiguration.updateProperty(name, value); onObjectsModified([object]); @@ -749,6 +754,8 @@ export const CompactObjectPropertiesEditor = ({ project={project} behaviorMetadata={behaviorMetadata} behavior={behavior} + behaviorOverriding={null} + initialInstance={null} object={object} onBehaviorUpdated={() => {}} resourceManagementProps={resourceManagementProps} diff --git a/newIDE/app/src/ObjectEditor/Editors/CustomObjectPropertiesEditor/index.js b/newIDE/app/src/ObjectEditor/Editors/CustomObjectPropertiesEditor/index.js index 3fcd355d6..71875ffe2 100644 --- a/newIDE/app/src/ObjectEditor/Editors/CustomObjectPropertiesEditor/index.js +++ b/newIDE/app/src/ObjectEditor/Editors/CustomObjectPropertiesEditor/index.js @@ -439,7 +439,11 @@ const CustomObjectPropertiesEditor = (props: Props) => { defaultValueProperties: customObjectEventsBasedObject ? customObjectEventsBasedObject.getPropertyDescriptors() : null, - getProperties: instance => instance.getProperties(), + getPropertyValue: (instance, name) => + instance + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (instance, name, value) => { instance.updateProperty(name, value); }, diff --git a/newIDE/app/src/ObjectEditor/Editors/ObjectPropertiesEditor.js b/newIDE/app/src/ObjectEditor/Editors/ObjectPropertiesEditor.js index 25540f958..9a9dc8a91 100644 --- a/newIDE/app/src/ObjectEditor/Editors/ObjectPropertiesEditor.js +++ b/newIDE/app/src/ObjectEditor/Editors/ObjectPropertiesEditor.js @@ -42,7 +42,11 @@ const ObjectPropertiesEditor = (props: Props) => { const propertiesSchema = propertiesMapToSchema({ properties, defaultValueProperties: null, - getProperties: object => object.getProperties(), + getPropertyValue: (object, name) => + object + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (object, name, value) => object.updateProperty(name, value), }); diff --git a/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.js b/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.js index 7661380e6..e7e4bbe89 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.js @@ -39,10 +39,18 @@ export type ObjectAnchor = { }; const getPropertyValue = ( + name: string, properties: gdMapStringPropertyDescriptor, - name: string + behaviorOverriding: gdBehavior | null ): CustomObjectConfiguration_EdgeAnchor => - properties.has(name) + behaviorOverriding && behaviorOverriding.hasPropertyValue(name) + ? gd.CustomObjectConfiguration.getEdgeAnchorFromString( + behaviorOverriding + .getProperties() + .get(name) + .getValue() + ) + : properties.has(name) ? gd.CustomObjectConfiguration.getEdgeAnchorFromString( properties.get(name).getValue() ) @@ -64,7 +72,8 @@ const getDefaultAnchor = () => ({ */ export const getObjectAnchor = ( eventBasedObjectVariant: gdEventsBasedObjectVariant, - objectName: string + objectName: string, + initialInstance: gdInitialInstance ): ObjectAnchor => { const objects = eventBasedObjectVariant.getObjects(); if (!objects.hasObjectNamed(objectName)) { @@ -75,10 +84,31 @@ export const getObjectAnchor = ( return getDefaultAnchor(); } const properties = childObject.getBehavior('Anchor').getProperties(); - const leftEdgeAnchor = getPropertyValue(properties, 'leftEdgeAnchor'); - const topEdgeAnchor = getPropertyValue(properties, 'topEdgeAnchor'); - const rightEdgeAnchor = getPropertyValue(properties, 'rightEdgeAnchor'); - const bottomEdgeAnchor = getPropertyValue(properties, 'bottomEdgeAnchor'); + const behaviorOverriding = initialInstance.hasBehaviorOverridingNamed( + 'Anchor' + ) + ? initialInstance.getBehaviorOverriding('Anchor') + : null; + const leftEdgeAnchor = getPropertyValue( + 'leftEdgeAnchor', + properties, + behaviorOverriding + ); + const topEdgeAnchor = getPropertyValue( + 'topEdgeAnchor', + properties, + behaviorOverriding + ); + const rightEdgeAnchor = getPropertyValue( + 'rightEdgeAnchor', + properties, + behaviorOverriding + ); + const bottomEdgeAnchor = getPropertyValue( + 'bottomEdgeAnchor', + properties, + behaviorOverriding + ); return { leftEdgeAnchor, topEdgeAnchor, rightEdgeAnchor, bottomEdgeAnchor }; }; @@ -274,6 +304,14 @@ export class LayoutedInstance { return []; } + hasBehaviorOverridingNamed(name: string): boolean { + return this.instance.hasBehaviorOverridingNamed(name); + } + + getBehaviorOverriding(name: string): gdBehavior { + return this.instance.getBehaviorOverriding(name); + } + serializeTo(element: gdSerializerElement) {} unserializeFrom(element: gdSerializerElement) {} @@ -337,7 +375,8 @@ export const getLayoutedRenderedInstance = ( const objectAnchor = getObjectAnchor( eventBasedObjectVariant, - layoutedInstance.getObjectName() + layoutedInstance.getObjectName(), + initialInstance ); const leftEdgeAnchor = objectAnchor ? objectAnchor.leftEdgeAnchor diff --git a/newIDE/app/src/ProjectManager/ExtensionsProperties.js b/newIDE/app/src/ProjectManager/ExtensionsProperties.js index fc5058ec9..0b16ed5fb 100644 --- a/newIDE/app/src/ProjectManager/ExtensionsProperties.js +++ b/newIDE/app/src/ProjectManager/ExtensionsProperties.js @@ -23,10 +23,12 @@ function ExtensionsProperties(props: Props) { const propertiesSchema = propertiesMapToSchema({ properties, defaultValueProperties: null, - getProperties: instance => + getPropertyValue: (instance, name) => project .getExtensionProperties() - .getAllExtensionProperties(extension.getName(), project), + .getAllExtensionProperties(extension.getName(), project) + .get(name) + .getValue(), onUpdateProperty: (instance, propertyName, newValue) => { if ( project diff --git a/newIDE/app/src/ProjectManager/ExternalLayoutTreeViewItemContent.js b/newIDE/app/src/ProjectManager/ExternalLayoutTreeViewItemContent.js index cf38a4cc7..b660f26b0 100644 --- a/newIDE/app/src/ProjectManager/ExternalLayoutTreeViewItemContent.js +++ b/newIDE/app/src/ProjectManager/ExternalLayoutTreeViewItemContent.js @@ -199,7 +199,12 @@ export class ExternalLayoutTreeViewItemContent implements TreeViewItemContent { this.getIndex() + 1 ); - unserializeFromJSObject(newExternalLayout, copiedExternalLayout); + unserializeFromJSObject( + newExternalLayout, + copiedExternalLayout, + 'unserializeFrom', + project + ); // Unserialization has overwritten the name. newExternalLayout.setName(newName); diff --git a/newIDE/app/src/PropertiesEditor/PropertiesEditorSchema.js b/newIDE/app/src/PropertiesEditor/PropertiesEditorSchema.js index 059117298..6dd761db3 100644 --- a/newIDE/app/src/PropertiesEditor/PropertiesEditorSchema.js +++ b/newIDE/app/src/PropertiesEditor/PropertiesEditorSchema.js @@ -16,6 +16,7 @@ export type ValueFieldCommonProperties = {| getLabel?: Instance => string, getDescription?: Instance => string, hideLabel?: boolean, + isHighlighted?: Instance => boolean, getExtraDescription?: Instance => string, hasImpactOnAllOtherFields?: boolean, canBeUnlimitedUsingMinus1?: boolean, @@ -32,8 +33,13 @@ export type ValueFieldCommonProperties = {| export type PrimitiveValueField = | {| valueType: 'number', - getValue: Instance => number, + getValue: Instance => number | null, setValue: (instance: Instance, newValue: number) => void, + getChoices?: ?() => Array<{| + value: string, + label: string, + labelIsUserDefined?: boolean, + |}>, /** Only supported on non compact property editors. */ getEndAdornment?: Instance => {| label: string, @@ -46,7 +52,7 @@ export type PrimitiveValueField = |} | {| valueType: 'string', - getValue: Instance => string, + getValue: Instance => string | null, setValue: (instance: Instance, newValue: string) => void, getChoices?: ?() => Array<{| value: string, @@ -63,7 +69,7 @@ export type PrimitiveValueField = |} | {| valueType: 'boolean', - getValue: Instance => boolean, + getValue: Instance => boolean | null, setValue: (instance: Instance, newValue: boolean) => void, ...ValueFieldCommonProperties, |} diff --git a/newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.js b/newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.js index 6b13d51bc..8e44db463 100644 --- a/newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.js +++ b/newIDE/app/src/PropertiesEditor/PropertiesMapToSchema.js @@ -36,14 +36,11 @@ const createField = ( value: boolean ) => void, defaultValue: string | null, - getProperties: (instance: Instance) => any, - object: ?gdObject + object: ?gdObject, + showcaseNonDefaultValues: boolean ): ?Field => { - const propertyDescription = property.getDescription(); + const propertyName = property.getLabel(); const getLabel = (instance: Instance) => { - const propertyName = getProperties(instance) - .get(name) - .getLabel(); if (propertyName) return propertyName; return ( name.charAt(0).toUpperCase() + @@ -53,17 +50,27 @@ const createField = ( .join(' ') ); }; + const propertyDescription = property.getDescription(); const getDescription = () => propertyDescription; + + const measurementUnit = property.getMeasurementUnit(); + // TODO Pass this object in the schema instead of building an UI element here. + // It will allow to use these data to make a different component for the + // compact and regular UI. + const enumeratedMeasurementUnit = { + shortLabel: getMeasurementUnitShortLabel(measurementUnit), + label: measurementUnit.getLabel(), + description: measurementUnit.getDescription(), + elementsWithWords: measurementUnit.getElementsWithWords(), + }; const getEndAdornment = (instance: Instance) => { - const property = getProperties(instance).get(name); - const measurementUnit = property.getMeasurementUnit(); return { - label: getMeasurementUnitShortLabel(measurementUnit), + label: enumeratedMeasurementUnit.shortLabel, tooltipContent: ( ), }; @@ -74,16 +81,30 @@ const createField = ( ? 'advanced' : 'basic'; + const getValueForString = (instance: Instance): string => + getStringValue(instance, name); + const getValueForNumber = (instance: Instance): number => + getNumberValue(instance, name); + const defaultValueNumber = + defaultValue !== null ? parseFloat(defaultValue) || 0 : null; + const isHighlightedForNumber = (instance: gdInitialInstance) => { + return ( + showcaseNonDefaultValues && + getValueForNumber(instance) !== defaultValueNumber + ); + }; + const isHighlightedForString = (instance: gdInitialInstance) => { + return ( + showcaseNonDefaultValues && getValueForString(instance) !== defaultValue + ); + }; + const valueType = property.getType().toLowerCase(); if (valueType === 'number') { - const defaultValueNumber = - defaultValue !== null ? parseFloat(defaultValue) || 0 : null; - const getValue = (instance: Instance): number => - getNumberValue(instance, name); const getEndAdornmentIcon = defaultValueNumber !== null ? (instance: gdInitialInstance) => { - return getValue(instance) === defaultValueNumber + return getValueForNumber(instance) === defaultValueNumber ? null : className => ; } @@ -100,7 +121,7 @@ const createField = ( return { name, valueType, - getValue, + getValue: getValueForNumber, setValue, defaultValue: defaultValueNumber, getLabel, @@ -114,12 +135,13 @@ const createField = ( getEndAdornmentIcon, onClickEndAdornment, visibility, + isHighlighted: isHighlightedForNumber, }; } else if (valueType === 'string' || valueType === '') { return { name, valueType: 'string', - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, @@ -128,21 +150,25 @@ const createField = ( getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'boolean') { + const defaultValueBoolean = defaultValue ? defaultValue === 'true' : null; + const getValue = (instance: Instance): boolean => + getBooleanValue(instance, name); return { name, valueType, - getValue: (instance: Instance): boolean => - getBooleanValue(instance, name), + getValue, setValue: (instance: Instance, newValue: boolean) => { setBooleanValue(instance, name, newValue); }, - defaultValue: defaultValue ? defaultValue === 'true' : null, + defaultValue: defaultValueBoolean, getLabel, getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'choice' || valueType === 'numberwithchoices') { // Choice is a "string" (with a selector for the user in the UI) @@ -160,19 +186,35 @@ const createField = ( .toJSArray() .map(value => ({ value, label: value })); - return { - name, - valueType: 'string', - getChoices: () => [...choices, ...deprecatedChoices], - getValue: (instance: Instance): string => getStringValue(instance, name), - setValue: (instance: Instance, newValue: string) => { - setStringValue(instance, name, newValue); - }, - getLabel, - getDescription, - hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), - visibility, - }; + return valueType === 'numberwithchoices' + ? { + name, + valueType: 'number', + getChoices: () => [...choices, ...deprecatedChoices], + getValue: getValueForNumber, + setValue: (instance: Instance, newValue: number) => { + setNumberValue(instance, name, newValue); + }, + getLabel, + getDescription, + hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), + visibility, + isHighlighted: isHighlightedForNumber, + } + : { + name, + valueType: 'string', + getChoices: () => [...choices, ...deprecatedChoices], + getValue: getValueForString, + setValue: (instance: Instance, newValue: string) => { + setStringValue(instance, name, newValue); + }, + getLabel, + getDescription, + hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), + visibility, + isHighlighted: isHighlightedForString, + }; } else if (valueType === 'behavior') { const behaviorType = property.getExtraInfo().size() > 0 ? property.getExtraInfo().at(0) : ''; @@ -202,13 +244,14 @@ const createField = ( getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'leaderboardid') { // LeaderboardId is a "string" (with a selector in the UI) return { name, valueType: 'leaderboardId', - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, @@ -216,6 +259,7 @@ const createField = ( getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'resource') { // Resource is a "string" (with a selector in the UI) @@ -226,7 +270,7 @@ const createField = ( name, valueType: 'resource', resourceKind: kind, - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, @@ -234,12 +278,13 @@ const createField = ( getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'color') { return { name, valueType: 'color', - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, @@ -247,12 +292,13 @@ const createField = ( getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'multilinestring') { return { name, valueType: 'multilinestring', - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, @@ -260,6 +306,7 @@ const createField = ( getDescription, hasImpactOnAllOtherFields: property.hasImpactOnOtherProperties(), visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'objectanimationname') { return { @@ -285,13 +332,14 @@ const createField = ( }, name, valueType: 'string', - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, getLabel, getDescription, visibility, + isHighlighted: isHighlightedForString, }; } else if (valueType === 'keyboardkey') { return { @@ -305,13 +353,14 @@ const createField = ( }, name, valueType: 'string', - getValue: (instance: Instance): string => getStringValue(instance, name), + getValue: getValueForString, setValue: (instance: Instance, newValue: string) => { setStringValue(instance, name, newValue); }, getLabel, getDescription, visibility, + isHighlighted: isHighlightedForString, }; } else { console.error( @@ -426,18 +475,18 @@ type CommonProps = {| | gdPropertiesContainer | gdMapStringPropertyDescriptor | null, - getProperties: (instance: Instance) => any, object?: ?gdObject, visibility?: 'All' | 'Basic' | 'Advanced' | 'Deprecated' | 'Basic-Quick', quickCustomizationVisibilities?: gdQuickCustomizationVisibilitiesContainer, + showcaseNonDefaultValues?: boolean, |}; export const effectPropertiesMapToSchema = ({ defaultValueProperties, - getProperties, object, visibility = 'All', quickCustomizationVisibilities, + showcaseNonDefaultValues, }: { ...CommonProps, defaultValueProperties: gdMapStringPropertyDescriptor, @@ -445,10 +494,10 @@ export const effectPropertiesMapToSchema = ({ return adaptablePropertiesMapToSchema({ properties: defaultValueProperties, defaultValueProperties, - getProperties, object, visibility, quickCustomizationVisibilities, + showcaseNonDefaultValues, getNumberValue: (instance: Instance, propertyName: string): number => instance.hasDoubleParameter(propertyName) ? instance.getDoubleParameter(propertyName) @@ -493,13 +542,15 @@ export const effectPropertiesMapToSchema = ({ const propertiesMapToSchema = ({ properties, defaultValueProperties, - getProperties, + getPropertyValue, onUpdateProperty, object, visibility = 'All', quickCustomizationVisibilities, + showcaseNonDefaultValues, }: { ...CommonProps, + getPropertyValue: (instance: Instance, propertyName: string) => string, properties: gdMapStringPropertyDescriptor, onUpdateProperty: ( instance: Instance, @@ -510,30 +561,19 @@ const propertiesMapToSchema = ({ return adaptablePropertiesMapToSchema({ properties, defaultValueProperties, - getProperties, object, visibility, quickCustomizationVisibilities, + showcaseNonDefaultValues, getNumberValue: (instance: Instance, propertyName: string): number => { - return ( - parseFloat( - getProperties(instance) - .get(propertyName) - .getValue() - ) || 0 - ); // Consider a missing value as 0 to avoid propagating NaN. + // Consider a missing value as 0 to avoid propagating NaN. + return parseFloat(getPropertyValue(instance, propertyName)) || 0; }, getStringValue: (instance: Instance, propertyName: string): string => { - return getProperties(instance) - .get(propertyName) - .getValue(); + return getPropertyValue(instance, propertyName); }, getBooleanValue: (instance: Instance, propertyName: string): boolean => { - return ( - getProperties(instance) - .get(propertyName) - .getValue() === 'true' - ); + return getPropertyValue(instance, propertyName) === 'true'; }, setNumberValue: (instance: Instance, propertyName: string, value: number) => onUpdateProperty(instance, propertyName, '' + value), @@ -550,10 +590,10 @@ const propertiesMapToSchema = ({ const adaptablePropertiesMapToSchema = ({ properties, defaultValueProperties, - getProperties, object, visibility = 'All', quickCustomizationVisibilities, + showcaseNonDefaultValues, getNumberValue, getStringValue, getBooleanValue, @@ -684,8 +724,8 @@ const adaptablePropertiesMapToSchema = ({ setStringValue, setBooleanValue, rowPropertyDefaultValue, - getProperties, - object + object, + !!showcaseNonDefaultValues ); if (field) { @@ -720,8 +760,8 @@ const adaptablePropertiesMapToSchema = ({ ? defaultValueProperties.get(name).getValue() : '' : null, - getProperties, - object + object, + !!showcaseNonDefaultValues ); } if (field) { diff --git a/newIDE/app/src/PropertiesEditor/index.js b/newIDE/app/src/PropertiesEditor/index.js index d60140673..d750aa67b 100644 --- a/newIDE/app/src/PropertiesEditor/index.js +++ b/newIDE/app/src/PropertiesEditor/index.js @@ -406,7 +406,7 @@ const PropertiesEditor = ({ const { setValue } = field; return ( {children} diff --git a/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js b/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js index 1003748a6..042846962 100644 --- a/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js +++ b/newIDE/app/src/QuickCustomization/QuickBehaviorsTweaker.js @@ -37,7 +37,11 @@ const QuickBehaviorPropertiesEditor = ({ return propertiesMapToSchema({ properties: behavior.getProperties(), defaultValueProperties: null, - getProperties: behavior => behavior.getProperties(), + getPropertyValue: (behavior, name) => + behavior + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (behavior, name, value) => { behavior.updateProperty(name, value); }, diff --git a/newIDE/app/src/QuickCustomization/QuickTitleTweaker.js b/newIDE/app/src/QuickCustomization/QuickTitleTweaker.js index 2d1171af7..bde4425ec 100644 --- a/newIDE/app/src/QuickCustomization/QuickTitleTweaker.js +++ b/newIDE/app/src/QuickCustomization/QuickTitleTweaker.js @@ -58,7 +58,11 @@ const QuickObjectPropertiesEditor = ({ const schema = propertiesMapToSchema({ properties, defaultValueProperties: null, - getProperties: object => object.getProperties(), + getPropertyValue: (object, name) => + object + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (object, name, value) => object.updateProperty(name, value), object, diff --git a/newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js b/newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js index 391b90afb..78c984e0f 100644 --- a/newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js +++ b/newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js @@ -157,7 +157,11 @@ const ResourcePropertiesEditor = React.forwardRef< const resourceSchema = propertiesMapToSchema({ properties, defaultValueProperties: null, - getProperties: resource => resource.getProperties(), + getPropertyValue: (resource, name) => + resource + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (resource, name, value) => { resource.updateProperty(name, value); forceUpdate(); diff --git a/newIDE/app/src/SceneEditor/BehaviorSharedPropertiesEditor.js b/newIDE/app/src/SceneEditor/BehaviorSharedPropertiesEditor.js index 3541d5d03..8fbf30de7 100644 --- a/newIDE/app/src/SceneEditor/BehaviorSharedPropertiesEditor.js +++ b/newIDE/app/src/SceneEditor/BehaviorSharedPropertiesEditor.js @@ -23,7 +23,11 @@ export default class BehaviorSharedPropertiesEditor extends React.Component behavior.getProperties(), + getPropertyValue: (behavior, name) => + behavior + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (behavior, name, value) => { behavior.updateProperty(name, value); }, diff --git a/newIDE/app/src/SceneEditor/CustomObjectExtractor/CustomObjectExtractor.js b/newIDE/app/src/SceneEditor/CustomObjectExtractor/CustomObjectExtractor.js index e5f88ff6d..6a1fbc376 100644 --- a/newIDE/app/src/SceneEditor/CustomObjectExtractor/CustomObjectExtractor.js +++ b/newIDE/app/src/SceneEditor/CustomObjectExtractor/CustomObjectExtractor.js @@ -77,7 +77,12 @@ export const extractAsCustomObject = ({ let isRenderedIn3D = null; for (const serializedInstance of serializedSelection) { const instance = new gd.InitialInstance(); - unserializeFromJSObject(instance, serializedInstance); + unserializeFromJSObject( + instance, + serializedInstance, + 'unserializeFrom', + project + ); layer = instance.getLayer(); zOrder = Math.max(zOrder, instance.getZOrder()); diff --git a/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js b/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js index c75e65f78..c38b9b38f 100644 --- a/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js +++ b/newIDE/app/src/SceneEditor/InstanceOrObjectPropertiesEditorContainer.js @@ -175,8 +175,10 @@ export const InstanceOrObjectPropertiesEditorContainer = React.forwardRef< layersContainer={layersContainer} project={project} projectScopedContainersAccessor={projectScopedContainersAccessor} + resourceManagementProps={resourceManagementProps} unsavedChanges={unsavedChanges} i18n={i18n} + canOverrideBehaviorProperties={!!eventsFunctionsExtension} /> ) : !!objects.length && lastSelectionType === 'object' ? ( behaviorSharedData.getProperties(), + getPropertyValue: (sharedDataContent, name) => + behaviorSharedData + .getProperties() + .get(name) + .getValue(), onUpdateProperty: (sharedDataContent, name, value) => { behaviorSharedData.updateProperty(name, value); }, diff --git a/newIDE/app/src/SceneEditor/index.js b/newIDE/app/src/SceneEditor/index.js index 513353797..fd06835a2 100644 --- a/newIDE/app/src/SceneEditor/index.js +++ b/newIDE/app/src/SceneEditor/index.js @@ -571,7 +571,12 @@ export default class SceneEditor extends React.Component { const justAddedInstances = changes.addedInstances.map(addedInstance => { const instance: gdInitialInstance = this.props.initialInstances.insertNewInitialInstance(); - unserializeFromJSObject(instance, addedInstance); + unserializeFromJSObject( + instance, + addedInstance, + 'unserializeFrom', + this.props.project + ); return instance; }); if (justAddedInstances.length) { @@ -2416,6 +2421,7 @@ export default class SceneEditor extends React.Component { .map(instance => serializeToJSObject(instance)); const newInstances = addSerializedInstances({ + project: this.props.project, instancesContainer: this.props.initialInstances, copyReferential: [-2 * MOVEMENT_BIG_DELTA, -2 * MOVEMENT_BIG_DELTA], serializedInstances: serializedSelection, @@ -2452,6 +2458,7 @@ export default class SceneEditor extends React.Component { if (x === null || y === null || instancesContent === null) return; const newInstances = addSerializedInstances({ + project: this.props.project, instancesContainer: this.props.initialInstances, copyReferential: [x, y], serializedInstances: instancesContent, @@ -2517,7 +2524,12 @@ export default class SceneEditor extends React.Component { for (const serializedInstance of serializedSelection) { const instance = new gd.InitialInstance(); - unserializeFromJSObject(instance, serializedInstance); + unserializeFromJSObject( + instance, + serializedInstance, + 'unserializeFrom', + project + ); newExternalLayout .getInitialInstances() .insertInitialInstance(instance) diff --git a/newIDE/app/src/UI/CompactTextAreaField/index.js b/newIDE/app/src/UI/CompactTextAreaField/index.js index 109b4430c..3327d04bf 100644 --- a/newIDE/app/src/UI/CompactTextAreaField/index.js +++ b/newIDE/app/src/UI/CompactTextAreaField/index.js @@ -18,7 +18,6 @@ const styles = { textOverflow: 'ellipsis', lineHeight: '17px', maxHeight: 34, // 2 * lineHeight to limit to 2 lines. - opacity: 0.7, }, }; @@ -34,6 +33,7 @@ export type CompactTextAreaFieldProps = {| placeholder?: MessageDescriptor, rows?: number, maxLength?: number, + labelColor?: 'primary' | 'secondary', |}; export const CompactTextAreaField = ({ @@ -48,6 +48,7 @@ export const CompactTextAreaField = ({ rows, maxLength, onSubmit, + labelColor, }: CompactTextAreaFieldProps) => { const idToUse = React.useRef(id || makeTimestampedId()); @@ -87,7 +88,11 @@ export const CompactTextAreaField = ({ }, }} > - + {label} diff --git a/newIDE/app/src/UI/CompactToggleField/index.js b/newIDE/app/src/UI/CompactToggleField/index.js index e5df300cc..4d5f1eecd 100644 --- a/newIDE/app/src/UI/CompactToggleField/index.js +++ b/newIDE/app/src/UI/CompactToggleField/index.js @@ -24,7 +24,7 @@ type Props = {| disabled?: boolean, fullWidth?: boolean, hideTooltip?: boolean, - labelColor?: 'primary', + labelColor?: 'primary' | 'secondary', |}; export const CompactToggleField = (props: Props) => { diff --git a/newIDE/app/src/stories/componentStories/LayoutEditor/CompactInstancePropertiesEditor.stories.js b/newIDE/app/src/stories/componentStories/LayoutEditor/CompactInstancePropertiesEditor.stories.js index 46ca65af7..cacaedadf 100644 --- a/newIDE/app/src/stories/componentStories/LayoutEditor/CompactInstancePropertiesEditor.stories.js +++ b/newIDE/app/src/stories/componentStories/LayoutEditor/CompactInstancePropertiesEditor.stories.js @@ -11,6 +11,7 @@ import paperDecorator from '../../PaperDecorator'; import { CompactInstancePropertiesEditor } from '../../../InstancesEditor/CompactInstancePropertiesEditor'; import SerializedObjectDisplay from '../../SerializedObjectDisplay'; import DragAndDropContextProvider from '../../../UI/DragAndDrop/DragAndDropContextProvider'; +import fakeResourceManagementProps from '../../FakeResourceManagement'; export default { title: 'LayoutEditor/CompactInstancePropertiesEditor', @@ -36,10 +37,14 @@ export const InstanceSprite2d = () => ( instances={[testProject.testSpriteObjectInstance]} editInstanceVariables={action('edit instance variables')} onGetInstanceSize={() => [100, 101, 102]} - editObjectInPropertiesPanel={action('edit object')} + editObjectInPropertiesPanel={action( + 'edit object in property panel' + )} tileMapTileSelection={null} onSelectTileMapTile={() => {}} isVariableListLocked={false} + canOverrideBehaviorProperties={true} + resourceManagementProps={fakeResourceManagementProps} /> )} @@ -65,10 +70,14 @@ export const InstanceCube3d = () => ( instances={[testProject.testLayoutInstance2]} editInstanceVariables={action('edit instance variables')} onGetInstanceSize={() => [100, 101, 102]} - editObjectInPropertiesPanel={action('edit object')} + editObjectInPropertiesPanel={action( + 'edit object in property panel' + )} tileMapTileSelection={null} onSelectTileMapTile={() => {}} isVariableListLocked={false} + canOverrideBehaviorProperties={true} + resourceManagementProps={fakeResourceManagementProps} /> )} @@ -94,10 +103,14 @@ export const InstanceTextInput = () => ( instances={[testProject.testLayoutInstance3]} editInstanceVariables={action('edit instance variables')} onGetInstanceSize={() => [120, 40, 0]} - editObjectInPropertiesPanel={action('edit object')} + editObjectInPropertiesPanel={action( + 'edit object in property panel' + )} tileMapTileSelection={null} onSelectTileMapTile={() => {}} isVariableListLocked={false} + canOverrideBehaviorProperties={true} + resourceManagementProps={fakeResourceManagementProps} /> )}