Compare commits

...

29 Commits

Author SHA1 Message Date
Florian Rival
839869aa7b Add hint that mapper behaviors should be used for platformer 2025-05-07 15:16:32 +02:00
D8H
8312bbe4f8 Fix 3D Physics "apply force/impulse toward position" action (#7600) 2025-05-06 21:04:02 +02:00
Clément Pasteau
bb77a71f26 Bump to 5.5.230 (#7593)
Do not show in changelog
2025-05-06 10:10:48 +02:00
D8H
4353469554 Fix bitmap text alignment and rotation (#7597)
* Fix BBText rotation
2025-05-05 20:10:21 +02:00
Clément Pasteau
46ea431f62 Fix firebase tests (#7594)
Do not show in changelog
2025-05-05 15:16:39 +02:00
github-actions[bot]
2d29d43355 Update translations [skip ci] (#7577)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-05-05 11:22:08 +02:00
Clément Pasteau
28a4e253a1 Update desktop & web icons with latest GDevelop branding (#7578) 2025-05-05 10:22:40 +02:00
D8H
f3dcb8eec8 Fix asset export for objects with unused children overriding (#7590) 2025-05-05 09:53:11 +02:00
Florian Rival
bf9e38ff31 Improve descriptions of boolean logic conditions
Only show in developer changelog
2025-05-04 16:08:36 +02:00
D8H
87649b9def Ensure variants downloaded from the store comply to their events-based object (#7587) 2025-05-02 12:51:58 +02:00
D8H
60d332e872 Fix rendered custom objects in the editor with an overriding of children configuration (#7585)
- Fix children overriding to only apply to the default variant at runtime
2025-05-02 12:51:14 +02:00
D8H
bdab12b1e6 Fix variant edition: forbid to edit variants from the store (they should be duplicated instead) (#7586) 2025-05-02 12:05:49 +02:00
Florian Rival
02f795f2c1 Update AI requests wording 2025-04-29 09:52:19 +02:00
AlexandreS
6e64d8521f Submit ai question with Ctrl+Enter or Go to button of mobile soft keyboard (#7579)
Don't show in changelog
2025-04-28 18:51:39 +02:00
Clément Pasteau
0e9aea1c9d Fix showing Checkered background on sprite project resources (#7580)
Do not show in changelog
2025-04-28 18:31:24 +02:00
Clément Pasteau
99901bf539 Bump newIDE version (#7576) 2025-04-28 12:07:23 +02:00
github-actions[bot]
53f1d745f5 Update translations [skip ci] (#7572)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-04-28 10:28:31 +02:00
D8H
b72034a475 Fix the sentence parameter of "loading progress" condition (#7574) 2025-04-28 10:27:04 +02:00
AlexandreS
926f6a2c56 Remove useless param when changing user subscription (#7573)
Don't show in changelog
2025-04-25 17:20:23 +02:00
D8H
32cc6a3109 Fix an anchor behavior test (#7571)
- Don't show in changelogs
2025-04-25 11:04:48 +02:00
github-actions[bot]
f9ab65155d Update translations [skip ci] (#7561)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-25 10:57:40 +02:00
github-actions[bot]
d700a9d26d Update extension translations [skip ci] (#7565)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-04-25 10:57:08 +02:00
Florian Rival
297b88ed60 Improve description of the condition to compare a timer value 2025-04-24 17:14:52 +02:00
Aurélien Vivet
06cef654b0 Add explanatory tooltip to game analytics graph (#7559) 2025-04-24 15:17:14 +02:00
D8H
c5dd26c93b Remove another unused import (#7568)
Don't show in changelog
2025-04-24 12:28:21 +02:00
D8H
e0f3b221bd Remove unused import (#7567)
Don't show in changelog
2025-04-24 12:07:43 +02:00
D8H
896f56e850 Allow to switch between variants of custom objects (#7403)
- Variants allows to restyle custom objects
- They can be customized with the graphical editor
- The asset store will progressively use them notably for UI elements (buttons, sliders)
2025-04-24 11:05:00 +02:00
Aurélien Vivet
cee43ce9df Fix typo in GuidedLessons.js (#7566)
Do not show in changelog
2025-04-24 10:11:56 +02:00
D8H
5e61712e55 Fix various issues with the 3D physics engine (#7564)
* Fix the box shape to always be oriented on Z
* Fix memory leaks
2025-04-23 19:55:06 +02:00
290 changed files with 5032 additions and 1558 deletions

View File

@@ -59,36 +59,44 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
// end of compatibility code
extension
.AddCondition("Or",
_("Or"),
_("Check if one of the sub conditions is true"),
_("If one of these conditions is true:"),
"",
"res/conditions/or24_black.png",
"res/conditions/or_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();
extension
.AddCondition("And",
_("And"),
_("Check if all sub conditions are true"),
_("If all of these conditions are true:"),
"",
"res/conditions/and24_black.png",
"res/conditions/and_black.png")
.AddCondition(
"Or",
_("Or"),
_("Checks if at least one sub-condition is true. If no "
"sub-condition is specified, it will always be false. "
"This is rarely used — multiple events and sub-events are "
"usually a better approach."),
_("If one of these conditions is true:"),
"",
"res/conditions/or24_black.png",
"res/conditions/or_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();
extension
.AddCondition(
"Not",
_("Not"),
_("Return the contrary of the result of the sub conditions"),
_("Invert the logical result of these conditions:"),
"And",
_("And"),
_("Checks if all sub-conditions are true. If no sub-condition is "
"specified, it will always be false. This is rarely needed, as "
"events already check all conditions before running actions."),
_("If all of these conditions are true:"),
"",
"res/conditions/not24_black.png",
"res/conditions/not_black.png")
"res/conditions/and24_black.png",
"res/conditions/and_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();
extension
.AddCondition("Not",
_("Not"),
_("Returns the opposite of the sub-condition(s) result. "
"This is rarely needed, as most conditions can be "
"inverted or expressed more simply."),
_("Invert the logical result of these conditions:"),
"",
"res/conditions/not24_black.png",
"res/conditions/not_black.png")
.SetCanHaveSubInstructions()
.MarkAsAdvanced();

View File

@@ -182,7 +182,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in background for a scene (between 0 and 1)."),
_("_PARAM0_ loading progress"),
_("_PARAM1_ loading progress"),
_(""),
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")

View File

@@ -43,7 +43,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
.AddCondition("CompareTimer",
_("Value of a scene timer"),
_("Compare the elapsed time of a scene timer. This "
"condition doesn't start the timer."),
"condition doesn't start the timer and will always be "
"false if the timer was not started previously (whatever "
"the comparison being made)."),
_("The timer _PARAM1_ _PARAM2_ _PARAM3_ seconds"),
"",

View File

@@ -0,0 +1,157 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "EventsBasedObjectVariantHelper.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectGroup.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
namespace gd {
void EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
const gd::Project &project, gd::EventsBasedObject &eventsBasedObject) {
auto &defaultObjects = eventsBasedObject.GetDefaultVariant().GetObjects();
for (const auto &variant :
eventsBasedObject.GetVariants().GetInternalVector()) {
auto &objects = variant->GetObjects();
// Delete extra objects
for (auto it = objects.GetObjects().begin();
it != objects.GetObjects().end(); ++it) {
const auto &objectName = it->get()->GetName();
if (!defaultObjects.HasObjectNamed(objectName)) {
variant->GetInitialInstances().RemoveInitialInstancesOfObject(
objectName);
// Do it in last because it unalloc objectName.
objects.RemoveObject(objectName);
--it;
}
}
for (const auto &defaultObject : defaultObjects.GetObjects()) {
const auto &objectName = defaultObject->GetName();
const auto &defaultVariables = defaultObject->GetVariables();
const auto &defaultBehaviors = defaultObject->GetAllBehaviorContents();
// Copy missing objects
if (!objects.HasObjectNamed(objectName)) {
objects.InsertObject(*defaultObject,
defaultObjects.GetObjectPosition(objectName));
objects.AddMissingObjectsInRootFolder();
continue;
}
// Change object types
auto &object = objects.GetObject(objectName);
if (object.GetType() != defaultObject->GetType()) {
// Keep a copy of the old object.
auto oldObject = objects.GetObject(objectName);
objects.RemoveObject(objectName);
objects.InsertObject(*defaultObject,
defaultObjects.GetObjectPosition(objectName));
object.CopyWithoutConfiguration(oldObject);
objects.AddMissingObjectsInRootFolder();
}
// Copy missing behaviors
auto &behaviors = object.GetAllBehaviorContents();
for (const auto &pair : defaultBehaviors) {
const auto &behaviorName = pair.first;
const auto &defaultBehavior = pair.second;
if (object.HasBehaviorNamed(behaviorName) &&
object.GetBehavior(behaviorName).GetTypeName() !=
defaultBehavior->GetTypeName()) {
object.RemoveBehavior(behaviorName);
}
if (!object.HasBehaviorNamed(behaviorName)) {
auto *behavior = object.AddNewBehavior(
project, defaultBehavior->GetTypeName(), behaviorName);
gd::SerializerElement element;
defaultBehavior->SerializeTo(element);
behavior->UnserializeFrom(element);
}
}
// Delete extra behaviors
for (auto it = behaviors.begin(); it != behaviors.end(); ++it) {
const auto &behaviorName = it->first;
if (!defaultObject->HasBehaviorNamed(behaviorName)) {
object.RemoveBehavior(behaviorName);
--it;
}
}
// Sort and copy missing variables
auto &variables = object.GetVariables();
for (size_t defaultVariableIndex = 0;
defaultVariableIndex < defaultVariables.Count();
defaultVariableIndex++) {
const auto &variableName =
defaultVariables.GetNameAt(defaultVariableIndex);
const auto &defaultVariable =
defaultVariables.Get(defaultVariableIndex);
auto variableIndex = variables.GetPosition(variableName);
if (variableIndex == gd::String::npos) {
variables.Insert(variableName, defaultVariable, defaultVariableIndex);
} else {
variables.Move(variableIndex, defaultVariableIndex);
}
if (variables.Get(variableName).GetType() != defaultVariable.GetType()) {
variables.Remove(variableName);
variables.Insert(variableName, defaultVariable, defaultVariableIndex);
}
}
// Remove extra variables
auto variableToRemoveCount = variables.Count() - defaultVariables.Count();
for (size_t iteration = 0; iteration < variableToRemoveCount;
iteration++) {
variables.Remove(variables.GetNameAt(variables.Count() - 1));
}
// Remove extra instance variables
variant->GetInitialInstances().IterateOverInstances(
[&objectName,
&defaultVariables](gd::InitialInstance &initialInstance) {
if (initialInstance.GetObjectName() != objectName) {
return false;
}
auto &instanceVariables = initialInstance.GetVariables();
for (size_t instanceVariableIndex = 0;
instanceVariableIndex < instanceVariables.Count();
instanceVariableIndex++) {
const auto &variableName =
defaultVariables.GetNameAt(instanceVariableIndex);
if (!defaultVariables.Has(variableName)) {
instanceVariables.Remove(variableName);
}
}
return false;
});
}
auto &defaultObjectGroups =
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjectGroups();
auto &objectGroups = variant->GetObjects().GetObjectGroups();
auto objectGroupsCount = objectGroups.Count();
// Clear groups
for (size_t index = 0; index < objectGroupsCount; index++) {
objectGroups.Remove(objectGroups.Get(0).GetName());
}
// Copy groups
for (size_t index = 0; index < defaultObjectGroups.Count(); index++) {
objectGroups.Insert(defaultObjectGroups.Get(index), index);
}
}
}
} // namespace gd

View File

@@ -0,0 +1,26 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
namespace gd {
class EventsBasedObject;
class Project;
} // namespace gd
namespace gd {
class GD_CORE_API EventsBasedObjectVariantHelper {
public:
/**
* @brief Apply the changes done on events-based object children to all its
* variants.
*/
static void
ComplyVariantsToEventsBasedObject(const gd::Project &project,
gd::EventsBasedObject &eventsBasedObject);
};
} // namespace gd

View File

@@ -17,6 +17,7 @@
#include "GDCore/IDE/Project/ResourcesRenamer.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/CustomBehavior.h"
#include "GDCore/Project/CustomObjectConfiguration.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
@@ -75,6 +76,16 @@ void ObjectAssetSerializer::SerializeTo(
cleanObject->SerializeTo(objectAssetElement.AddChild("object"));
if (project.HasEventsBasedObject(object.GetType())) {
SerializerElement &variantsElement =
objectAssetElement.AddChild("variants");
variantsElement.ConsiderAsArrayOf("variant");
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
project, object, variantsElement, alreadyUsedVariantIdentifiers);
}
SerializerElement &resourcesElement =
objectAssetElement.AddChild("resources");
resourcesElement.ConsiderAsArrayOf("resource");
@@ -108,4 +119,47 @@ void ObjectAssetSerializer::SerializeTo(
objectAssetElement.AddChild("customization");
customizationElement.ConsiderAsArrayOf("empty");
}
void ObjectAssetSerializer::SerializeUsedVariantsTo(
gd::Project &project, const gd::Object &object,
SerializerElement &variantsElement,
std::unordered_set<gd::String> &alreadyUsedVariantIdentifiers) {
if (!project.HasEventsBasedObject(object.GetType())) {
return;
}
const auto &eventsBasedObject =
project.GetEventsBasedObject(object.GetType());
const auto &variants = eventsBasedObject.GetVariants();
const auto *customObjectConfiguration =
dynamic_cast<const gd::CustomObjectConfiguration *>(
&object.GetConfiguration());
const auto &variantName = customObjectConfiguration->GetVariantName();
if (!variants.HasVariantNamed(variantName) &&
(customObjectConfiguration
->IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() ||
customObjectConfiguration
->IsForcedToOverrideEventsBasedObjectChildrenConfiguration())) {
return;
}
const auto &variantIdentifier =
object.GetType() + gd::PlatformExtension::GetNamespaceSeparator() +
variantName;
auto insertResult = alreadyUsedVariantIdentifiers.insert(variantIdentifier);
if (insertResult.second) {
const auto &variant = variants.HasVariantNamed(variantName)
? variants.GetVariant(variantName)
: eventsBasedObject.GetDefaultVariant();
SerializerElement &pairElement = variantsElement.AddChild("variant");
pairElement.SetAttribute("objectType", object.GetType());
SerializerElement &variantElement = pairElement.AddChild("variant");
variant.SerializeTo(variantElement);
for (auto &object : variant.GetObjects().GetObjects()) {
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
project, *object, variantsElement, alreadyUsedVariantIdentifiers);
}
}
}
} // namespace gd

View File

@@ -6,6 +6,7 @@
#pragma once
#include <map>
#include <vector>
#include <unordered_set>
#include "GDCore/String.h"
@@ -52,6 +53,11 @@ private:
ObjectAssetSerializer(){};
static gd::String GetObjectExtensionName(const gd::Object &object);
static void SerializeUsedVariantsTo(
gd::Project &project, const gd::Object &object,
SerializerElement &variantsElement,
std::unordered_set<gd::String> &alreadyUsedVariantIdentifiers);
};
} // namespace gd

View File

@@ -6,6 +6,7 @@
#include "ObjectVariableHelper.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectGroup.h"
@@ -173,6 +174,7 @@ void ObjectVariableHelper::ApplyChangesToObjects(
groupVariablesContainer.Get(variableName),
variablesContainer.Count());
}
// TODO Check what happens if 2 variables exchange their names.
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
@@ -215,6 +217,7 @@ void ObjectVariableHelper::ApplyChangesToObjectInstances(
destinationVariablesContainer.Remove(variableName);
}
}
// TODO Check what happens if 2 variables exchange their names.
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
@@ -236,6 +239,66 @@ void ObjectVariableHelper::ApplyChangesToObjectInstances(
}
}
}
return false;
});
}
void ObjectVariableHelper::ApplyChangesToVariants(
gd::EventsBasedObject &eventsBasedObject, const gd::String &objectName,
const gd::VariablesChangeset &changeset) {
auto &defaultVariablesContainer = eventsBasedObject.GetDefaultVariant()
.GetObjects()
.GetObject(objectName)
.GetVariables();
for (auto &variant : eventsBasedObject.GetVariants().GetInternalVector()) {
if (!variant->GetObjects().HasObjectNamed(objectName)) {
continue;
}
auto &object = variant->GetObjects().GetObject(objectName);
auto &variablesContainer = object.GetVariables();
for (const gd::String &variableName : changeset.removedVariableNames) {
variablesContainer.Remove(variableName);
}
for (const gd::String &variableName : changeset.addedVariableNames) {
if (variablesContainer.Has(variableName)) {
// It can happens if a child-object already had the variable but it was
// missing in other variant child-object.
continue;
}
variablesContainer.Insert(variableName,
defaultVariablesContainer.Get(variableName),
variablesContainer.Count());
}
// TODO Check what happens if 2 variables exchange their names.
for (const auto &pair : changeset.oldToNewVariableNames) {
const gd::String &oldVariableName = pair.first;
const gd::String &newVariableName = pair.second;
if (variablesContainer.Has(newVariableName)) {
// It can happens if a child-object already had the variable but it was
// missing in other variant child-object.
variablesContainer.Remove(oldVariableName);
} else {
variablesContainer.Rename(oldVariableName, newVariableName);
}
}
// Apply type changes
for (const gd::String &variableName : changeset.valueChangedVariableNames) {
size_t index = variablesContainer.GetPosition(variableName);
if (variablesContainer.Has(variableName) &&
variablesContainer.Get(variableName).GetType() !=
defaultVariablesContainer.Get(variableName).GetType()) {
variablesContainer.Remove(variableName);
variablesContainer.Insert(
variableName, defaultVariablesContainer.Get(variableName), index);
}
}
gd::ObjectVariableHelper::ApplyChangesToObjectInstances(
variablesContainer, variant->GetInitialInstances(), objectName,
changeset);
}
}
} // namespace gd

View File

@@ -8,6 +8,7 @@
#include "GDCore/Project/VariablesContainer.h"
namespace gd {
class EventsBasedObject;
class InitialInstancesContainer;
class ObjectsContainersList;
class ObjectsContainer;
@@ -53,7 +54,7 @@ public:
* Objects can be added during the group edition and may not necessarily have
* all the variables initially shared by the group.
*
* \see gd::GroupVariableHelper::MergeVariableContainers
* \see gd::ObjectVariableHelper::MergeVariableContainers
*/
static void FillMissingGroupVariablesToObjects(
gd::ObjectsContainer &globalObjectsContainer,
@@ -72,16 +73,21 @@ public:
const gd::ObjectGroup &objectGroup,
const gd::VariablesChangeset &changeset);
/**
* @brief Apply the changes done on an object to all its instances.
*/
static void ApplyChangesToObjectInstances(
gd::VariablesContainer &objectVariablesContainer,
gd::InitialInstancesContainer &initialInstancesContainer,
const gd::String &objectName, const gd::VariablesChangeset &changeset);
private:
static void ApplyChangesToVariableContainer(
const gd::VariablesContainer &originalVariablesContainer,
gd::VariablesContainer &destinationVariablesContainer,
const gd::VariablesChangeset &changeset, bool shouldApplyValueChanges);
/**
* @brief Apply the changes done on events-based object child to all its
* variants.
*/
static void ApplyChangesToVariants(gd::EventsBasedObject &eventsBasedObject,
const gd::String &objectName,
const gd::VariablesChangeset &changeset);
};
} // namespace gd

View File

@@ -314,6 +314,12 @@ void ProjectBrowserHelper::ExposeProjectObjects(
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
auto eventsBasedObject = eventsBasedObjectUniquePtr.get();
worker.Launch(eventsBasedObject->GetObjects());
for (auto &&variantUniquePtr :
eventsBasedObject->GetVariants().GetInternalVector()) {
auto variant = variantUniquePtr.get();
worker.Launch(variant->GetObjects());
}
}
}
};

View File

@@ -2129,6 +2129,26 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject(
groups[g].RenameObject(oldName, newName);
}
}
for (auto &variant : eventsBasedObject.GetVariants().GetInternalVector()) {
auto &variantObjects = variant->GetObjects();
auto &variantObjectGroups = variantObjects.GetObjectGroups();
if (isObjectGroup) {
if (variantObjectGroups.Has(oldName)) {
variantObjectGroups.Get(oldName).SetName(newName);
}
// Object groups can't have instances or be in other groups
}
else {
if (variantObjects.HasObjectNamed(oldName)) {
variantObjects.GetObject(oldName).SetName(newName);
}
variant->GetInitialInstances().RenameInstancesOfObject(oldName, newName);
for (std::size_t g = 0; g < variantObjectGroups.size(); ++g) {
variantObjectGroups[g].RenameObject(oldName, newName);
}
}
}
}
void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(

View File

@@ -19,6 +19,7 @@ using namespace gd;
void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& objectConfiguration) {
project = objectConfiguration.project;
variantName = objectConfiguration.variantName;
objectContent = objectConfiguration.objectContent;
animations = objectConfiguration.animations;
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
@@ -165,6 +166,7 @@ void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const
animations.SerializeTo(animatableElement);
}
element.SetAttribute("variant", variantName);
if (IsOverridingEventsBasedObjectChildrenConfiguration()) {
auto &childrenContentElement = element.AddChild("childrenContent");
for (auto &pair : childObjectConfigurations) {
@@ -184,6 +186,7 @@ void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
animations.UnserializeFrom(animatableElement);
}
variantName = element.GetStringAttribute("variant");
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration =
element.HasChild("childrenContent");
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
@@ -247,9 +250,26 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
}
const auto &eventsBasedObject = project->GetEventsBasedObject(GetType());
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
configuration.ExposeResources(worker);
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
configuration.ExposeResources(worker);
}
} else {
if (variantName.empty() ||
!eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
for (auto &childObject :
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
childObject->GetConfiguration().ExposeResources(worker);
}
} else {
for (auto &childObject : eventsBasedObject.GetVariants()
.GetVariant(variantName)
.GetObjects()
.GetObjects()) {
childObject->GetConfiguration().ExposeResources(worker);
}
}
}
}

View File

@@ -29,9 +29,9 @@ namespace gd {
* "resource".
*/
class CustomObjectConfiguration : public gd::ObjectConfiguration {
public:
CustomObjectConfiguration(const Project& project_, const String& type_)
: project(&project_), isMarkedAsOverridingEventsBasedObjectChildrenConfiguration(false) {
public:
CustomObjectConfiguration(const Project &project_, const String &type_)
: project(&project_) {
SetType(type_);
}
std::unique_ptr<gd::ObjectConfiguration> Clone() const override;
@@ -66,6 +66,18 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
void ExposeResources(gd::ArbitraryResourceWorker& worker) override;
/**
* \brief Get the name of the events-based object variant used by this custom object.
*/
const gd::String &GetVariantName() const { return variantName; };
/**
* \brief Set the name of the events-based object variant used by this custom object.
*/
void SetVariantName(const gd::String &variantName_) {
variantName = variantName_;
}
bool IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const;
bool IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() const {
@@ -145,6 +157,7 @@ protected:
gd::SerializerElement objectContent;
std::unordered_set<gd::String> unfoldedChildren;
gd::String variantName = "";
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration = false;
mutable std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;

View File

@@ -17,19 +17,13 @@ EventsBasedObject::EventsBasedObject()
isAnimatable(false),
isTextContainer(false),
isInnerAreaFollowingParentSize(false),
isUsingLegacyInstancesRenderer(false),
areaMinX(0),
areaMinY(0),
areaMinZ(0),
areaMaxX(64),
areaMaxY(64),
areaMaxZ(64),
objectsContainer(gd::ObjectsContainer::SourceType::Object) {
isUsingLegacyInstancesRenderer(false) {
}
EventsBasedObject::~EventsBasedObject() {}
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
void EventsBasedObject::SerializeToExternal(SerializerElement& element) const {
element.SetAttribute("defaultName", defaultName);
if (isRenderedIn3D) {
element.SetBoolAttribute("is3D", true);
@@ -44,20 +38,16 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
element.SetBoolAttribute("isInnerAreaFollowingParentSize", true);
}
element.SetBoolAttribute("isUsingLegacyInstancesRenderer", isUsingLegacyInstancesRenderer);
element.SetIntAttribute("areaMinX", areaMinX);
element.SetIntAttribute("areaMinY", areaMinY);
element.SetIntAttribute("areaMinZ", areaMinZ);
element.SetIntAttribute("areaMaxX", areaMaxX);
element.SetIntAttribute("areaMaxY", areaMaxY);
element.SetIntAttribute("areaMaxZ", areaMaxZ);
// The EventsBasedObjectVariant SerializeTo method override the name.
// AbstractEventsBasedEntity::SerializeTo must be done after.
defaultVariant.SerializeTo(element);
AbstractEventsBasedEntity::SerializeTo(element);
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
objectsContainer.SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
objectsContainer.GetObjectGroups().SerializeTo(element.AddChild("objectsGroups"));
}
layers.SerializeLayersTo(element.AddChild("layers"));
initialInstances.SerializeTo(element.AddChild("instances"));
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
SerializeToExternal(element);
variants.SerializeVariantsTo(element.AddChild("variants"));
}
void EventsBasedObject::UnserializeFrom(gd::Project& project,
@@ -68,36 +58,22 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project,
isTextContainer = element.GetBoolAttribute("isTextContainer", false);
isInnerAreaFollowingParentSize =
element.GetBoolAttribute("isInnerAreaFollowingParentSize", false);
areaMinX = element.GetIntAttribute("areaMinX", 0);
areaMinY = element.GetIntAttribute("areaMinY", 0);
areaMinZ = element.GetIntAttribute("areaMinZ", 0);
areaMaxX = element.GetIntAttribute("areaMaxX", 64);
areaMaxY = element.GetIntAttribute("areaMaxY", 64);
areaMaxZ = element.GetIntAttribute("areaMaxZ", 64);
defaultVariant.UnserializeFrom(project, element);
defaultVariant.SetName("");
AbstractEventsBasedEntity::UnserializeFrom(project, element);
objectsContainer.UnserializeObjectsFrom(project, element.GetChild("objects"));
if (element.HasChild("objectsFolderStructure")) {
objectsContainer.UnserializeFoldersFrom(project, element.GetChild("objectsFolderStructure", 0));
}
objectsContainer.AddMissingObjectsInRootFolder();
objectsContainer.GetObjectGroups().UnserializeFrom(
element.GetChild("objectsGroups"));
if (element.HasChild("layers")) {
layers.UnserializeLayersFrom(element.GetChild("layers"));
} else {
layers.Reset();
if (element.HasChild("variants")) {
variants.UnserializeVariantsFrom(project, element.GetChild("variants"));
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
if (element.HasChild("isUsingLegacyInstancesRenderer")) {
isUsingLegacyInstancesRenderer =
element.GetBoolAttribute("isUsingLegacyInstancesRenderer", false);
}
else {
// Compatibility with GD <= 5.4.212
isUsingLegacyInstancesRenderer = initialInstances.GetInstancesCount() == 0;
isUsingLegacyInstancesRenderer = GetInitialInstances().GetInstancesCount() == 0;
// end of compatibility code
}
}

View File

@@ -7,6 +7,8 @@
#include <vector>
#include "GDCore/Project/AbstractEventsBasedEntity.h"
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/Project/EventsBasedObjectVariantsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/LayersContainer.h"
@@ -162,18 +164,38 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
*/
bool IsTextContainer() const { return isTextContainer; }
/**
* \brief Get the default variant of the custom object.
*/
const gd::EventsBasedObjectVariant& GetDefaultVariant() const { return defaultVariant; }
/**
* \brief Get the default variant of the custom object.
*/
gd::EventsBasedObjectVariant& GetDefaultVariant() { return defaultVariant; }
/**
* \brief Get the variants of the custom object.
*/
const gd::EventsBasedObjectVariantsContainer& GetVariants() const { return variants; }
/**
* \brief Get the variants of the custom object.
*/
gd::EventsBasedObjectVariantsContainer& GetVariants() { return variants; }
/** \name Layers
*/
///@{
/**
* \brief Get the layers of the custom object.
*/
const gd::LayersContainer& GetLayers() const { return layers; }
const gd::LayersContainer& GetLayers() const { return defaultVariant.GetLayers(); }
/**
* \brief Get the layers of the custom object.
*/
gd::LayersContainer& GetLayers() { return layers; }
gd::LayersContainer& GetLayers() { return defaultVariant.GetLayers(); }
///@}
/** \name Child objects
@@ -183,14 +205,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \brief Get the objects of the custom object.
*/
gd::ObjectsContainer& GetObjects() {
return objectsContainer;
return defaultVariant.GetObjects();
}
/**
* \brief Get the objects of the custom object.
*/
const gd::ObjectsContainer& GetObjects() const {
return objectsContainer;
return defaultVariant.GetObjects();
}
///@}
@@ -201,14 +223,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \brief Get the instances of the custom object.
*/
gd::InitialInstancesContainer& GetInitialInstances() {
return initialInstances;
return defaultVariant.GetInitialInstances();
}
/**
* \brief Get the instances of the custom object.
*/
const gd::InitialInstancesContainer& GetInitialInstances() const {
return initialInstances;
return defaultVariant.GetInitialInstances();
}
/**
@@ -219,14 +241,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMinX() const {
return areaMinX;
return defaultVariant.GetAreaMinX();
}
/**
* \brief Set the left bound of the custom object.
*/
void SetAreaMinX(int areaMinX_) {
areaMinX = areaMinX_;
void SetAreaMinX(int areaMinX) {
defaultVariant.SetAreaMinX(areaMinX);
}
/**
@@ -237,14 +259,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMinY() const {
return areaMinY;
return defaultVariant.GetAreaMinY();
}
/**
* \brief Set the top bound of the custom object.
*/
void SetAreaMinY(int areaMinY_) {
areaMinY = areaMinY_;
void SetAreaMinY(int areaMinY) {
defaultVariant.SetAreaMinY(areaMinY);
}
/**
@@ -255,14 +277,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMinZ() const {
return areaMinZ;
return defaultVariant.GetAreaMinZ();
}
/**
* \brief Set the min Z bound of the custom object.
*/
void SetAreaMinZ(int areaMinZ_) {
areaMinZ = areaMinZ_;
void SetAreaMinZ(int areaMinZ) {
defaultVariant.SetAreaMinZ(areaMinZ);
}
/**
@@ -273,14 +295,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMaxX() const {
return areaMaxX;
return defaultVariant.GetAreaMaxX();
}
/**
* \brief Set the right bound of the custom object.
*/
void SetAreaMaxX(int areaMaxX_) {
areaMaxX = areaMaxX_;
void SetAreaMaxX(int areaMaxX) {
defaultVariant.SetAreaMaxX(areaMaxX);
}
/**
@@ -291,14 +313,14 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMaxY() const {
return areaMaxY;
return defaultVariant.GetAreaMaxY();
}
/**
* \brief Set the bottom bound of the custom object.
*/
void SetAreaMaxY(int areaMaxY_) {
areaMaxY = areaMaxY_;
void SetAreaMaxY(int areaMaxY) {
defaultVariant.SetAreaMaxY(areaMaxY);
}
/**
@@ -309,16 +331,22 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
* \see EventsBasedObject::GetInitialInstances
*/
int GetAreaMaxZ() const {
return areaMaxZ;
return defaultVariant.GetAreaMaxZ();
}
/**
* \brief Set the bottom bound of the custom object.
*/
void SetAreaMaxZ(int areaMaxZ_) {
areaMaxZ = areaMaxZ_;
void SetAreaMaxZ(int areaMaxZ) {
defaultVariant.SetAreaMaxZ(areaMaxZ);
}
///@}
/**
* @brief Serialize the events-based object for an extension in an external file.
* Variants are not serialized.
*/
void SerializeToExternal(SerializerElement& element) const;
void SerializeTo(SerializerElement& element) const override;
@@ -332,15 +360,8 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
bool isTextContainer;
bool isInnerAreaFollowingParentSize;
bool isUsingLegacyInstancesRenderer;
gd::InitialInstancesContainer initialInstances;
gd::LayersContainer layers;
gd::ObjectsContainer objectsContainer;
double areaMinX;
double areaMinY;
double areaMinZ;
double areaMaxX;
double areaMaxY;
double areaMaxZ;
gd::EventsBasedObjectVariant defaultVariant;
gd::EventsBasedObjectVariantsContainer variants;
};
} // namespace gd

View File

@@ -0,0 +1,71 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "EventsBasedObjectVariant.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
EventsBasedObjectVariant::EventsBasedObjectVariant()
: areaMinX(0), areaMinY(0), areaMinZ(0), areaMaxX(64), areaMaxY(64),
areaMaxZ(64), objectsContainer(gd::ObjectsContainer::SourceType::Object) {
}
EventsBasedObjectVariant::~EventsBasedObjectVariant() {}
void EventsBasedObjectVariant::SerializeTo(SerializerElement &element) const {
element.SetAttribute("name", name);
if (!GetAssetStoreAssetId().empty() && !GetAssetStoreOriginalName().empty()) {
element.SetAttribute("assetStoreAssetId", GetAssetStoreAssetId());
element.SetAttribute("assetStoreOriginalName", GetAssetStoreOriginalName());
}
element.SetIntAttribute("areaMinX", areaMinX);
element.SetIntAttribute("areaMinY", areaMinY);
element.SetIntAttribute("areaMinZ", areaMinZ);
element.SetIntAttribute("areaMaxX", areaMaxX);
element.SetIntAttribute("areaMaxY", areaMaxY);
element.SetIntAttribute("areaMaxZ", areaMaxZ);
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
objectsContainer.SerializeFoldersTo(
element.AddChild("objectsFolderStructure"));
objectsContainer.GetObjectGroups().SerializeTo(
element.AddChild("objectsGroups"));
layers.SerializeLayersTo(element.AddChild("layers"));
initialInstances.SerializeTo(element.AddChild("instances"));
}
void EventsBasedObjectVariant::UnserializeFrom(
gd::Project &project, const SerializerElement &element) {
name = element.GetStringAttribute("name");
assetStoreAssetId = element.GetStringAttribute("assetStoreAssetId");
assetStoreOriginalName = element.GetStringAttribute("assetStoreOriginalName");
areaMinX = element.GetIntAttribute("areaMinX", 0);
areaMinY = element.GetIntAttribute("areaMinY", 0);
areaMinZ = element.GetIntAttribute("areaMinZ", 0);
areaMaxX = element.GetIntAttribute("areaMaxX", 64);
areaMaxY = element.GetIntAttribute("areaMaxY", 64);
areaMaxZ = element.GetIntAttribute("areaMaxZ", 64);
objectsContainer.UnserializeObjectsFrom(project, element.GetChild("objects"));
if (element.HasChild("objectsFolderStructure")) {
objectsContainer.UnserializeFoldersFrom(
project, element.GetChild("objectsFolderStructure", 0));
}
objectsContainer.AddMissingObjectsInRootFolder();
objectsContainer.GetObjectGroups().UnserializeFrom(
element.GetChild("objectsGroups"));
if (element.HasChild("layers")) {
layers.UnserializeLayersFrom(element.GetChild("layers"));
} else {
layers.Reset();
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
}
} // namespace gd

View File

@@ -0,0 +1,229 @@
/*
* GDevelop Core
* Copyright 2008-2025 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/LayersContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/String.h"
#include <vector>
namespace gd {
class SerializerElement;
class Project;
} // namespace gd
namespace gd {
/**
* \brief Represents a variation of style of an events-based object.
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsBasedObjectVariant {
public:
EventsBasedObjectVariant();
virtual ~EventsBasedObjectVariant();
/**
* \brief Return a pointer to a new EventsBasedObjectVariant constructed from
* this one.
*/
EventsBasedObjectVariant *Clone() const {
return new EventsBasedObjectVariant(*this);
};
/**
* \brief Get the name of the variant.
*/
const gd::String &GetName() const { return name; };
/**
* \brief Set the name of the variant.
*/
EventsBasedObjectVariant &SetName(const gd::String &name_) {
name = name_;
return *this;
}
/** \name Layers
*/
///@{
/**
* \brief Get the layers of the variant.
*/
const gd::LayersContainer &GetLayers() const { return layers; }
/**
* \brief Get the layers of the variant.
*/
gd::LayersContainer &GetLayers() { return layers; }
///@}
/** \name Child objects
*/
///@{
/**
* \brief Get the objects of the variant.
*/
gd::ObjectsContainer &GetObjects() { return objectsContainer; }
/**
* \brief Get the objects of the variant.
*/
const gd::ObjectsContainer &GetObjects() const { return objectsContainer; }
///@}
/** \name Instances
*/
///@{
/**
* \brief Get the instances of the variant.
*/
gd::InitialInstancesContainer &GetInitialInstances() {
return initialInstances;
}
/**
* \brief Get the instances of the variant.
*/
const gd::InitialInstancesContainer &GetInitialInstances() const {
return initialInstances;
}
/**
* \brief Get the left bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMinX() const { return areaMinX; }
/**
* \brief Set the left bound of the variant.
*/
void SetAreaMinX(int areaMinX_) { areaMinX = areaMinX_; }
/**
* \brief Get the top bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMinY() const { return areaMinY; }
/**
* \brief Set the top bound of the variant.
*/
void SetAreaMinY(int areaMinY_) { areaMinY = areaMinY_; }
/**
* \brief Get the min Z bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMinZ() const { return areaMinZ; }
/**
* \brief Set the min Z bound of the variant.
*/
void SetAreaMinZ(int areaMinZ_) { areaMinZ = areaMinZ_; }
/**
* \brief Get the right bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMaxX() const { return areaMaxX; }
/**
* \brief Set the right bound of the variant.
*/
void SetAreaMaxX(int areaMaxX_) { areaMaxX = areaMaxX_; }
/**
* \brief Get the bottom bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMaxY() const { return areaMaxY; }
/**
* \brief Set the bottom bound of the variant.
*/
void SetAreaMaxY(int areaMaxY_) { areaMaxY = areaMaxY_; }
/**
* \brief Get the max Z bound of the variant.
*
* This is used only if there is any initial instances.
*
* \see EventsBasedObjectVariant::GetInitialInstances
*/
int GetAreaMaxZ() const { return areaMaxZ; }
/**
* \brief Set the bottom bound of the variant.
*/
void SetAreaMaxZ(int areaMaxZ_) { areaMaxZ = areaMaxZ_; }
///@}
/** \brief Change the object asset store id of this variant.
*/
void SetAssetStoreAssetId(const gd::String &assetStoreId_) {
assetStoreAssetId = assetStoreId_;
};
/** \brief Return the object asset store id of this variant.
*/
const gd::String &GetAssetStoreAssetId() const { return assetStoreAssetId; };
/** \brief Change the original name of the variant in the asset.
*/
void SetAssetStoreOriginalName(const gd::String &assetStoreOriginalName_) {
assetStoreOriginalName = assetStoreOriginalName_;
};
/** \brief Return the original name of the variant in the asset.
*/
const gd::String &GetAssetStoreOriginalName() const {
return assetStoreOriginalName;
};
void SerializeTo(SerializerElement &element) const;
void UnserializeFrom(gd::Project &project, const SerializerElement &element);
private:
gd::String name;
gd::InitialInstancesContainer initialInstances;
gd::LayersContainer layers;
gd::ObjectsContainer objectsContainer;
double areaMinX;
double areaMinY;
double areaMinZ;
double areaMaxX;
double areaMaxY;
double areaMaxZ;
/**
* The ID of the asset if the object comes from the store.
*/
gd::String assetStoreAssetId;
/**
* The original name of the variant in the asset if the object comes from the
* store.
*/
gd::String assetStoreOriginalName;
};
} // namespace gd

View File

@@ -0,0 +1,160 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <vector>
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
namespace gd {
class SerializerElement;
}
namespace gd {
/**
* \brief Used as a base class for classes that will own events-backed
* variants.
*
* \see gd::EventsBasedObjectVariantContainer
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsBasedObjectVariantsContainer
: private SerializableWithNameList<gd::EventsBasedObjectVariant> {
public:
EventsBasedObjectVariantsContainer() {}
EventsBasedObjectVariantsContainer(const EventsBasedObjectVariantsContainer &other) {
Init(other);
}
EventsBasedObjectVariantsContainer &operator=(const EventsBasedObjectVariantsContainer &other) {
if (this != &other) {
Init(other);
}
return *this;
}
/** \name Events Functions management
*/
///@{
/**
* \brief Check if the variant with the specified name exists.
*/
bool HasVariantNamed(const gd::String& name) const {
return Has(name);
}
/**
* \brief Get the variant with the specified name.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
gd::EventsBasedObjectVariant& GetVariant(const gd::String& name) {
return Get(name);
}
/**
* \brief Get the variant with the specified name.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
const gd::EventsBasedObjectVariant& GetVariant(const gd::String& name) const {
return Get(name);
}
/**
* \brief Get the variant at the specified index in the list.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
gd::EventsBasedObjectVariant& GetVariant(std::size_t index) {
return Get(index);
}
/**
* \brief Get the variant at the specified index in the list.
*
* \warning Trying to access to a not existing variant will result in
* undefined behavior.
*/
const gd::EventsBasedObjectVariant& GetVariant(std::size_t index) const {
return Get(index);
}
/**
* \brief Return the number of variants.
*/
std::size_t GetVariantsCount() const { return GetCount(); }
gd::EventsBasedObjectVariant& InsertNewVariant(const gd::String& name,
std::size_t position) {
return InsertNew(name, position);
}
gd::EventsBasedObjectVariant& InsertVariant(const gd::EventsBasedObjectVariant& object,
std::size_t position) {
return Insert(object, position);
}
void RemoveVariant(const gd::String& name) { return Remove(name); }
void ClearVariants() { return Clear(); }
void MoveVariant(std::size_t oldIndex, std::size_t newIndex) {
return Move(oldIndex, newIndex);
};
std::size_t GetVariantPosition(const gd::EventsBasedObjectVariant& eventsFunction) {
return GetPosition(eventsFunction);
};
/**
* \brief Provide a raw access to the vector containing the variants.
*/
const std::vector<std::unique_ptr<gd::EventsBasedObjectVariant>>& GetInternalVector()
const {
return elements;
};
/**
* \brief Provide a raw access to the vector containing the variants.
*/
std::vector<std::unique_ptr<gd::EventsBasedObjectVariant>>& GetInternalVector() {
return elements;
};
///@}
/** \name Serialization
*/
///@{
/**
* \brief Serialize events variants.
*/
void SerializeVariantsTo(SerializerElement& element) const {
return SerializeElementsTo("variant", element);
};
/**
* \brief Unserialize the events variants.
*/
void UnserializeVariantsFrom(gd::Project& project,
const SerializerElement& element) {
return UnserializeElementsFrom("variant", project, element);
};
///@}
protected:
/**
* Initialize object using another object. Used by copy-ctor and assign-op.
* Don't forget to update me if members were changed!
*/
void Init(const gd::EventsBasedObjectVariantsContainer& other) {
return SerializableWithNameList<gd::EventsBasedObjectVariant>::Init(other);
};
private:
};
} // namespace gd

View File

@@ -55,7 +55,7 @@ void EventsFunctionsExtension::Init(const gd::EventsFunctionsExtension& other) {
sceneVariables = other.GetSceneVariables();
}
void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
void EventsFunctionsExtension::SerializeTo(SerializerElement& element, bool isExternal) const {
element.SetAttribute("version", version);
element.SetAttribute("extensionNamespace", extensionNamespace);
element.SetAttribute("shortDescription", shortDescription);
@@ -102,8 +102,18 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
element.AddChild("eventsFunctions"));
eventsBasedBehaviors.SerializeElementsTo(
"eventsBasedBehavior", element.AddChild("eventsBasedBehaviors"));
eventsBasedObjects.SerializeElementsTo(
"eventsBasedObject", element.AddChild("eventsBasedObjects"));
if (isExternal) {
auto &eventsBasedObjectElement = element.AddChild("eventsBasedObjects");
eventsBasedObjectElement.ConsiderAsArrayOf("eventsBasedObject");
for (const auto &eventsBasedObject :
eventsBasedObjects.GetInternalVector()) {
eventsBasedObject->SerializeToExternal(
eventsBasedObjectElement.AddChild("eventsBasedObject"));
}
} else {
eventsBasedObjects.SerializeElementsTo(
"eventsBasedObject", element.AddChild("eventsBasedObjects"));
}
}
void EventsFunctionsExtension::UnserializeFrom(

View File

@@ -286,7 +286,14 @@ class GD_CORE_API EventsFunctionsExtension {
/**
* \brief Serialize the EventsFunctionsExtension to the specified element
*/
void SerializeTo(gd::SerializerElement& element) const;
void SerializeTo(gd::SerializerElement& element, bool isExternal = false) const;
/**
* \brief Serialize the EventsFunctionsExtension to the specified element
*/
void SerializeToExternal(gd::SerializerElement& element) const {
SerializeTo(element, true);
}
/**
* \brief Load the EventsFunctionsExtension from the specified element.

View File

@@ -42,8 +42,13 @@ void InitialInstancesContainer::IterateOverInstances(
}
void InitialInstancesContainer::IterateOverInstances(
const std::function< void(gd::InitialInstance &) >& func) {
for (auto& instance : initialInstances) func(instance);
const std::function< bool(gd::InitialInstance &) >& func) {
for (auto& instance : initialInstances) {
bool shouldStop = func(instance);
if (shouldStop) {
return;
}
}
}
void InitialInstancesContainer::IterateOverInstancesWithZOrdering(

View File

@@ -92,7 +92,7 @@ class GD_CORE_API InitialInstancesContainer {
* \see InitialInstanceFunctor
*/
void IterateOverInstances(
const std::function< void(gd::InitialInstance &) >& func);
const std::function< bool(gd::InitialInstance &) >& func);
/**
* Get the instances on the specified layer,

View File

@@ -41,6 +41,11 @@ Object::Object(const gd::String& name_,
}
void Object::Init(const gd::Object& object) {
CopyWithoutConfiguration(object);
configuration = object.configuration->Clone();
}
void Object::CopyWithoutConfiguration(const gd::Object& object) {
persistentUuid = object.persistentUuid;
name = object.name;
assetStoreId = object.assetStoreId;
@@ -51,8 +56,6 @@ void Object::Init(const gd::Object& object) {
for (auto& it : object.behaviors) {
behaviors[it.first] = gd::make_unique<gd::Behavior>(*it.second);
}
configuration = object.configuration->Clone();
}
gd::ObjectConfiguration& Object::GetConfiguration() { return *configuration; }

View File

@@ -82,6 +82,8 @@ class GD_CORE_API Object {
return gd::make_unique<gd::Object>(*this);
}
void CopyWithoutConfiguration(const gd::Object& object);
/**
* \brief Return the object configuration.
*/

View File

@@ -920,6 +920,7 @@ void Project::UnserializeAndInsertExtensionsFrom(
"eventsFunctionsExtension");
std::map<gd::String, size_t> extensionNameToElementIndex;
std::map<gd::String, gd::SerializerElement> objectTypeToVariantsElement;
// First, only unserialize behaviors and objects names.
// As event based objects can contains custom behaviors and custom objects,
@@ -938,6 +939,16 @@ void Project::UnserializeAndInsertExtensionsFrom(
? GetEventsFunctionsExtension(name)
: InsertNewEventsFunctionsExtension(
name, GetEventsFunctionsExtensionsCount());
// Backup the events-based object variants
for (auto &eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
gd::SerializerElement variantsElement;
eventsBasedObject->GetVariants().SerializeVariantsTo(variantsElement);
objectTypeToVariantsElement[gd::PlatformExtension::GetObjectFullType(
name, eventsBasedObject->GetName())] = variantsElement;
}
eventsFunctionsExtension.UnserializeExtensionDeclarationFrom(
*this, eventsFunctionsExtensionElement);
}
@@ -966,6 +977,15 @@ void Project::UnserializeAndInsertExtensionsFrom(
partiallyLoadedExtension
->UnserializeExtensionImplementationFrom(
*this, eventsFunctionsExtensionElement);
for (auto &pair : objectTypeToVariantsElement) {
auto &objectType = pair.first;
auto &variantsElement = pair.second;
auto &eventsBasedObject = GetEventsBasedObject(objectType);
eventsBasedObject.GetVariants().UnserializeVariantsFrom(*this,
variantsElement);
}
}
}

View File

@@ -0,0 +1,522 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering events of GDevelop Core.
*/
#include "catch.hpp"
#include <algorithm>
#include <initializer_list>
#include <map>
#include "GDCore/CommonTools.h"
#include "GDCore/IDE/EventsBasedObjectVariantHelper.h"
#include "DummyPlatform.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
#include "catch.hpp"
gd::InitialInstance *
GetFirstInstanceOf(const gd::String objectName,
gd::InitialInstancesContainer &initialInstances) {
gd::InitialInstance *variantInstance = nullptr;
initialInstances.IterateOverInstances(
[&variantInstance, &objectName](gd::InitialInstance &instance) {
if (instance.GetObjectName() == objectName) {
variantInstance = &instance;
return true;
}
return false;
});
return variantInstance;
}
gd::EventsBasedObject &SetupEventsBasedObject(gd::Project &project) {
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
auto &object = eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject", 0);
object.GetVariables().InsertNew("MyVariable").SetValue(123);
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
auto &instance =
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
instance.SetObjectName("MyChildObject");
instance.GetVariables().InsertNew("MyVariable").SetValue(111);
auto &objectGroup =
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew(
"MyObjectGroup");
objectGroup.AddObject("MyChildObject");
return eventsBasedObject;
}
TEST_CASE("EventsBasedObjectVariantHelper", "[common]") {
SECTION("Can add missing objects") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject2"));
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject3"));
}
SECTION("Can remove objects") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
variant.GetInitialInstances().InsertNewInitialInstance().SetObjectName(
"MyChildObject2");
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject(
"MyChildObject2") == true);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().RemoveObject("MyChildObject2");
eventsBasedObject.GetObjects().RemoveObject("MyChildObject3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject2") == false);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject3") == false);
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject(
"MyChildObject2") == false);
}
SECTION("Can change object type") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().RemoveObject("MyChildObject");
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::FakeObjectWithDefaultBehavior", "MyChildObject",
0);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects().GetObject("MyChildObject").GetType() ==
"MyExtension::FakeObjectWithDefaultBehavior");
REQUIRE(variant.GetInitialInstances().GetInstancesCount() == 1);
}
SECTION("Can add missing object groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew("MyObjectGroup2",
0);
eventsBasedObject.GetObjects()
.GetObjectGroups()
.InsertNew("MyObjectGroup3", 0)
.AddObject("MyChildObject");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(variantObjectGroups.Has("MyObjectGroup2"));
REQUIRE(variantObjectGroups.Has("MyObjectGroup3"));
REQUIRE(
variantObjectGroups.Get("MyObjectGroup").GetAllObjectsNames().size() ==
1);
REQUIRE(
variantObjectGroups.Get("MyObjectGroup2").GetAllObjectsNames().size() ==
0);
REQUIRE(
variantObjectGroups.Get("MyObjectGroup3").GetAllObjectsNames().size() ==
1);
}
SECTION("Can remove object groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
// Do the changes and launch the refactoring.
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew("MyObjectGroup2",
0);
eventsBasedObject.GetObjects().GetObjectGroups().InsertNew("MyObjectGroup3",
0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
eventsBasedObject.GetObjects().GetObjectGroups().Remove("MyObjectGroup2");
eventsBasedObject.GetObjects().GetObjectGroups().Remove("MyObjectGroup3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(variantObjectGroups.Has("MyObjectGroup2") == false);
REQUIRE(variantObjectGroups.Has("MyObjectGroup3") == false);
}
SECTION("Can add objects to groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &objectGroup =
eventsBasedObject.GetObjects().GetObjectGroups().Get("MyObjectGroup");
objectGroup.AddObject("MyChildObject2");
objectGroup.AddObject("MyChildObject3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(
variantObjectGroups.Get("MyObjectGroup").GetAllObjectsNames().size() ==
3);
}
SECTION("Can remove objects from groups") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject2", 0);
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject3", 0);
auto &objectGroup =
eventsBasedObject.GetObjects().GetObjectGroups().Get("MyObjectGroup");
objectGroup.AddObject("MyChildObject2");
objectGroup.AddObject("MyChildObject3");
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
objectGroup.RemoveObject("MyChildObject2");
objectGroup.RemoveObject("MyChildObject3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
auto &variantObjectGroups = variant.GetObjects().GetObjectGroups();
REQUIRE(variantObjectGroups.Has("MyObjectGroup"));
REQUIRE(
variantObjectGroups.Get("MyObjectGroup").GetAllObjectsNames().size() ==
1);
}
SECTION("Can add missing behaviors") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior2");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior"));
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior2"));
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior3"));
}
SECTION("Can remove missing behaviors") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior2");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior3");
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
object.RemoveBehavior("MyBehavior2");
object.RemoveBehavior("MyBehavior3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior"));
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior2") == false);
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior3") == false);
}
SECTION("Can change behavior type") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.RemoveBehavior("MyBehavior");
object.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
"MyBehavior");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.HasBehaviorNamed("MyBehavior"));
REQUIRE(variantObject.GetBehavior("MyBehavior").GetTypeName() ==
"MyExtension::MyOtherBehavior");
}
SECTION("Can add missing variables") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.GetVariables().InsertNew("MyVariable2", 1).SetValue(456);
object.GetVariables().InsertNew("MyVariable3", 2).SetValue(789);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetValue() == 123);
REQUIRE(variantObject.GetVariables().Get("MyVariable2").GetValue() == 456);
REQUIRE(variantObject.GetVariables().Get("MyVariable3").GetValue() == 789);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Has("MyVariable"));
REQUIRE(objectInstance->GetVariables().Has("MyVariable2") == false);
REQUIRE(objectInstance->GetVariables().Has("MyVariable3") == false);
}
}
SECTION("Can keep variable value") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
// Do the changes and launch the refactoring.
object.GetVariables().Get("MyVariable").SetValue(456);
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetValue() == 123);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Get("MyVariable").GetValue() ==
111);
}
}
SECTION("Must not propagate instance variable value changes") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
{
auto *objectInstance = GetFirstInstanceOf(
"MyChildObject", eventsBasedObject.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
objectInstance->GetVariables().Get("MyVariable").SetValue(222);
}
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Get("MyVariable").GetValue() ==
111);
}
}
SECTION("Can move variables") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.GetVariables().InsertNew("MyVariable2", 1).SetValue(456);
object.GetVariables().InsertNew("MyVariable3", 2).SetValue(789);
{
auto *objectInstance = GetFirstInstanceOf(
"MyChildObject", eventsBasedObject.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
objectInstance->GetVariables().Get("MyVariable2").SetValue(222);
objectInstance->GetVariables().Get("MyVariable3").SetValue(333);
}
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Do the changes and launch the refactoring.
object.GetVariables().Move(2, 0);
object.GetVariables().Get("MyVariable").SetValue(111);
object.GetVariables().Get("MyVariable2").SetValue(222);
object.GetVariables().Get("MyVariable3").SetValue(333);
REQUIRE(object.GetVariables().GetNameAt(0) == "MyVariable3");
REQUIRE(object.GetVariables().GetNameAt(1) == "MyVariable");
REQUIRE(object.GetVariables().GetNameAt(2) == "MyVariable2");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetValue() == 123);
REQUIRE(variantObject.GetVariables().Get("MyVariable2").GetValue() == 456);
REQUIRE(variantObject.GetVariables().Get("MyVariable3").GetValue() == 789);
REQUIRE(variantObject.GetVariables().GetNameAt(0) == "MyVariable3");
REQUIRE(variantObject.GetVariables().GetNameAt(1) == "MyVariable");
REQUIRE(variantObject.GetVariables().GetNameAt(2) == "MyVariable2");
}
SECTION("Can remove variables") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
object.GetVariables().InsertNew("MyVariable2", 1).SetValue(456);
object.GetVariables().InsertNew("MyVariable3", 2).SetValue(789);
{
auto *objectInstance = GetFirstInstanceOf(
"MyChildObject", eventsBasedObject.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
objectInstance->GetVariables().Get("MyVariable2").SetValue(222);
objectInstance->GetVariables().Get("MyVariable3").SetValue(333);
}
// Do the changes and launch the refactoring.
object.GetVariables().Remove("MyVariable2");
object.GetVariables().Remove("MyVariable3");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Has("MyVariable"));
REQUIRE(variantObject.GetVariables().Has("MyVariable2") == false);
REQUIRE(variantObject.GetVariables().Has("MyVariable3") == false);
{
auto *objectInstance =
GetFirstInstanceOf("MyChildObject", variant.GetInitialInstances());
REQUIRE(objectInstance != nullptr);
REQUIRE(objectInstance->GetVariables().Has("MyVariable"));
REQUIRE(objectInstance->GetVariables().Has("MyVariable2") == false);
REQUIRE(objectInstance->GetVariables().Has("MyVariable3") == false);
}
}
SECTION("Can change variable type") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsBasedObject = SetupEventsBasedObject(project);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
auto &object = eventsBasedObject.GetObjects().GetObject("MyChildObject");
// Do the changes and launch the refactoring.
object.GetVariables().Get("MyVariable").SetString("abc");
gd::EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
project, eventsBasedObject);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
auto &variantObject = variant.GetObjects().GetObject("MyChildObject");
REQUIRE(variantObject.GetVariables().Get("MyVariable").GetString() ==
"abc");
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject("MyVariable") ==
false);
}
}

View File

@@ -33,7 +33,132 @@ using namespace gd;
TEST_CASE("ObjectAssetSerializer", "[common]") {
SECTION("Can serialize custom objects as assets") {
SECTION("Can serialize custom objects as assets with variant") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
eventsBasedObject.SetFullName("My events based object");
eventsBasedObject.SetDescription("An events based object for test");
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChild", 0);
auto &childInstance =
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
childInstance.SetObjectName("MyChild");
auto &resourceManager = project.GetResourcesManager();
gd::ImageResource imageResource;
imageResource.SetName("assets/Idle.png");
imageResource.SetFile("assets/Idle.png");
imageResource.SetSmooth(true);
resourceManager.AddResource(imageResource);
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
gd::Object &object = layout.GetObjects().InsertNewObject(
project, "MyEventsExtension::MyEventsBasedObject", "MyObject", 0);
auto *spriteConfiguration =
dynamic_cast<gd::SpriteObject *>(&childObject.GetConfiguration());
REQUIRE(spriteConfiguration != nullptr);
{
gd::Animation animation;
animation.SetName("Idle");
animation.SetDirectionsCount(1);
auto &direction = animation.GetDirection(0);
gd::Sprite frame;
frame.SetImageName("assets/Idle.png");
direction.AddSprite(frame);
spriteConfiguration->GetAnimations().AddAnimation(animation);
}
SerializerElement assetElement;
std::vector<gd::String> usedResourceNames;
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
assetElement, usedResourceNames);
// This list is used to copy resource files.
REQUIRE(usedResourceNames.size() == 1);
REQUIRE(usedResourceNames[0] == "assets/Idle.png");
// Check that the project is left untouched.
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
REQUIRE(resourceManager.GetResource("assets/Idle.png").GetFile() ==
"assets/Idle.png");
REQUIRE(!resourceManager.HasResource("Idle.png"));
REQUIRE(assetElement.HasChild("objectAssets"));
auto &objectAssetsElement = assetElement.GetChild("objectAssets");
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
REQUIRE(objectAssetsElement.GetChildrenCount() == 1);
auto &objectAssetElement = objectAssetsElement.GetChild(0);
REQUIRE(objectAssetElement.HasChild("variants"));
auto &variantsElement = objectAssetElement.GetChild("variants");
variantsElement.ConsiderAsArrayOf("variant");
REQUIRE(variantsElement.GetChildrenCount() == 1);
auto &variantPairElement = variantsElement.GetChild(0);
REQUIRE(variantPairElement.GetStringAttribute("objectType") ==
"MyEventsExtension::MyEventsBasedObject");
REQUIRE(variantPairElement.HasChild("variant"));
auto &variantElement = variantPairElement.GetChild("variant");
REQUIRE(variantElement.GetStringAttribute("name") == "");
REQUIRE(variantElement.HasChild("objects"));
auto &objectsElement = variantElement.GetChild("objects");
objectsElement.ConsiderAsArrayOf("object");
REQUIRE(objectsElement.GetChildrenCount() == 1);
auto &childElement = objectsElement.GetChild(0);
REQUIRE(childElement.HasChild("animations"));
auto &animationsElement = childElement.GetChild("animations");
animationsElement.ConsiderAsArrayOf("animation");
REQUIRE(animationsElement.GetChildrenCount() == 1);
auto &animationElement = animationsElement.GetChild(0);
REQUIRE(animationElement.GetStringAttribute("name") == "Idle");
auto &directionsElement = animationElement.GetChild("directions");
directionsElement.ConsiderAsArrayOf("direction");
REQUIRE(directionsElement.GetChildrenCount() == 1);
auto &directionElement = directionsElement.GetChild(0);
auto &spritesElement = directionElement.GetChild("sprites");
spritesElement.ConsiderAsArrayOf("sprite");
REQUIRE(spritesElement.GetChildrenCount() == 1);
auto &spriteElement = spritesElement.GetChild(0);
REQUIRE(spriteElement.GetStringAttribute("image") == "assets/Idle.png");
REQUIRE(objectAssetElement.HasChild("requiredExtensions"));
auto &requiredExtensionsElement =
objectAssetElement.GetChild("requiredExtensions");
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
REQUIRE(requiredExtensionsElement.GetChildrenCount() == 1);
auto &requiredExtensionElement = requiredExtensionsElement.GetChild(0);
REQUIRE(requiredExtensionElement.GetStringAttribute("extensionName") ==
"MyEventsExtension");
// Resources are renamed according to asset script naming conventions.
REQUIRE(objectAssetElement.HasChild("resources"));
auto &resourcesElement = objectAssetElement.GetChild("resources");
resourcesElement.ConsiderAsArrayOf("resource");
REQUIRE(resourcesElement.GetChildrenCount() == 1);
{
auto &resourceElement = resourcesElement.GetChild(0);
REQUIRE(resourceElement.GetStringAttribute("name") == "assets/Idle.png");
REQUIRE(resourceElement.GetStringAttribute("file") == "assets/Idle.png");
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
}
// Resources used in object configuration are updated.
REQUIRE(objectAssetElement.HasChild("object"));
auto &objectElement = objectAssetElement.GetChild("object");
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
REQUIRE(objectElement.GetStringAttribute("type") ==
"MyEventsExtension::MyEventsBasedObject");
}
SECTION("Can serialize custom objects as assets with children overriding") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
@@ -59,6 +184,8 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
auto &configuration = object.GetConfiguration();
auto *customObjectConfiguration =
dynamic_cast<gd::CustomObjectConfiguration *>(&configuration);
customObjectConfiguration
->SetMarkedAsOverridingEventsBasedObjectChildrenConfiguration(true);
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(
&customObjectConfiguration->GetChildObjectConfiguration("MyChild"));
REQUIRE(spriteConfiguration != nullptr);

View File

@@ -150,7 +150,7 @@ TEST_CASE("ObjectContainersList (GetTypeOfObject)", "[common]") {
gd::Object &object1 = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject1", 0);
gd::Object &object2 = layout.GetObjects().InsertNewObject(
project, "FakeObjectWithDefaultBehavior", "MyObject2", 0);
project, "MyExtension::FakeObjectWithDefaultBehavior", "MyObject2", 0);
auto &group = layout.GetObjects().GetObjectGroups().InsertNew("MyGroup", 0);
group.AddObject(object1.GetName());

View File

@@ -1072,6 +1072,99 @@ TEST_CASE("WholeProjectRefactorer::ApplyRefactoringForVariablesContainer",
REQUIRE(instance.GetVariables().Get("MyRenamedVariable").GetValue() == 456);
}
SECTION("Can rename an object variable (in events-based object)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
auto &object = eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyChildObject", 0);
object.GetVariables().InsertNew("MyVariable").SetValue(123);
auto &instance =
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
instance.SetObjectName("MyChildObject");
instance.GetVariables().InsertNew("MyVariable").SetValue(456);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
gd::InitialInstance *variantInstance = nullptr;
variant.GetInitialInstances().IterateOverInstances(
[&variantInstance](gd::InitialInstance &instance) {
variantInstance = &instance;
return true;
});
REQUIRE(variantInstance != nullptr);
variant.GetObjects()
.GetObject("MyChildObject")
.GetVariables()
.Get("MyVariable")
.SetValue(111);
variantInstance->GetVariables().Get("MyVariable").SetValue(222);
auto &objectFunction =
eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
"MyObjectEventsFunction", 0);
gd::StandardEvent &event = dynamic_cast<gd::StandardEvent &>(
objectFunction.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
{
gd::Instruction action;
action.SetType("SetNumberObjectVariable");
action.SetParametersCount(4);
action.SetParameter(0, gd::Expression("MyChildObject"));
action.SetParameter(1, gd::Expression("MyVariable"));
action.SetParameter(2, gd::Expression("="));
action.SetParameter(3, gd::Expression("MyChildObject.MyVariable"));
event.GetActions().Insert(action);
}
// Do the changes and launch the refactoring.
object.GetVariables().ResetPersistentUuid();
gd::SerializerElement originalSerializedVariables;
object.GetVariables().SerializeTo(originalSerializedVariables);
object.GetVariables().Rename("MyVariable", "MyRenamedVariable");
auto changeset =
gd::WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
originalSerializedVariables, object.GetVariables());
REQUIRE(changeset.oldToNewVariableNames.size() == 1);
gd::WholeProjectRefactorer::ApplyRefactoringForObjectVariablesContainer(
project, object.GetVariables(), eventsBasedObject.GetInitialInstances(),
object.GetName(), changeset, originalSerializedVariables);
gd::ObjectVariableHelper::ApplyChangesToVariants(
eventsBasedObject, "MyChildObject", changeset);
REQUIRE(event.GetActions()[0].GetParameter(1).GetPlainString() ==
"MyRenamedVariable");
REQUIRE(event.GetActions()[0].GetParameter(3).GetPlainString() ==
"MyChildObject.MyRenamedVariable");
REQUIRE(eventsBasedObject.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(eventsBasedObject.GetObjects()
.GetObject("MyChildObject")
.GetVariables()
.Get("MyRenamedVariable")
.GetValue() == 123);
REQUIRE(instance.GetVariables().Get("MyRenamedVariable").GetValue() == 456);
REQUIRE(variant.GetObjects().HasObjectNamed("MyChildObject"));
REQUIRE(variant.GetObjects()
.GetObject("MyChildObject")
.GetVariables()
.Get("MyRenamedVariable")
.GetValue() == 111);
REQUIRE(
variantInstance->GetVariables().Get("MyRenamedVariable").GetValue() ==
222);
}
SECTION("Can delete an object variable") {
gd::Project project;
gd::Platform platform;

View File

@@ -1754,6 +1754,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsBasedObject.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "Object2", 0);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Create the objects container for the events function
gd::ObjectsContainer parametersObjectsContainer(
gd::ObjectsContainer::SourceType::Function);
@@ -1765,6 +1768,10 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject(
project, projectScopedContainers, eventsBasedObject, "Object1",
"Object3", /* isObjectGroup =*/false);
REQUIRE(variant.GetObjects().HasObjectNamed("Object1") == false);
REQUIRE(variant.GetObjects().HasObjectNamed("Object2") == true);
REQUIRE(variant.GetObjects().HasObjectNamed("Object3") == true);
REQUIRE(eventsBasedObject.GetObjects().GetObjectGroups().size() == 1);
REQUIRE(eventsBasedObject.GetObjects().GetObjectGroups()[0].Find(
"Object1") == false);
@@ -1772,6 +1779,17 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"Object2") == true);
REQUIRE(eventsBasedObject.GetObjects().GetObjectGroups()[0].Find(
"Object3") == true);
REQUIRE(variant.GetObjects().HasObjectNamed("Object1") == false);
REQUIRE(variant.GetObjects().HasObjectNamed("Object2") == true);
REQUIRE(variant.GetObjects().HasObjectNamed("Object3") == true);
REQUIRE(variant.GetObjects().GetObjectGroups().size() == 1);
REQUIRE(variant.GetObjects().GetObjectGroups()[0].Find("Object1") ==
false);
REQUIRE(variant.GetObjects().GetObjectGroups()[0].Find("Object2") ==
true);
REQUIRE(variant.GetObjects().GetObjectGroups()[0].Find("Object3") ==
true);
}
SECTION("Initial instances") {
@@ -1796,6 +1814,9 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
eventsBasedObject.GetInitialInstances().InsertInitialInstance(instance1);
eventsBasedObject.GetInitialInstances().InsertInitialInstance(instance2);
auto &variant = eventsBasedObject.GetVariants().InsertVariant(
eventsBasedObject.GetDefaultVariant(), 0);
// Create the objects container for the events function
gd::ObjectsContainer parametersObjectsContainer(
gd::ObjectsContainer::SourceType::Function);
@@ -1807,10 +1828,16 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject(
project, projectScopedContainers, eventsBasedObject, "Object1",
"Object3", /* isObjectGroup =*/false);
REQUIRE(eventsBasedObject.GetInitialInstances().HasInstancesOfObject(
"Object1") == false);
REQUIRE(eventsBasedObject.GetInitialInstances().HasInstancesOfObject(
"Object3") == true);
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject("Object1") ==
false);
REQUIRE(variant.GetInitialInstances().HasInstancesOfObject("Object3") ==
true);
}
SECTION("Events") {

View File

@@ -1,5 +1,5 @@
// @ts-check
describe.only('gdjs.AnchorRuntimeBehavior', () => {
describe('gdjs.AnchorRuntimeBehavior', () => {
it('can fill a custom object with an child', async () => {
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
const runtimeScene = new gdjs.TestRuntimeScene(runtimeGame);
@@ -8,6 +8,7 @@ describe.only('gdjs.AnchorRuntimeBehavior', () => {
const customObject = new gdjs.CustomRuntimeObject2D(runtimeScene, {
name: 'MyCustomObject',
type: 'MyExtension::MyLayoutedEventsBasedObject',
variant: '',
variables: [],
behaviors: [],
effects: [],

View File

@@ -507,14 +507,16 @@ module.exports = {
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
pixiResourcesLoader,
propertyOverridings
) {
super(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
pixiResourcesLoader,
propertyOverridings
);
const bbTextStyles = {
@@ -553,7 +555,9 @@ module.exports = {
gd.ObjectJsImplementation
);
const rawText = object.content.text;
const rawText = this._propertyOverridings.has('Text')
? this._propertyOverridings.get('Text')
: object.content.text;
if (rawText !== this._pixiObject.text) {
this._pixiObject.text = rawText;
}
@@ -614,7 +618,7 @@ module.exports = {
this._pixiObject.dirty = true;
}
if (this._instance.hasCustomSize()) {
if (this._instance.hasCustomSize() && this._pixiObject.width !== 0) {
const alignmentX =
object.content.align === 'right'
? 1

View File

@@ -103,7 +103,7 @@ namespace gdjs {
}
updatePosition(): void {
if (this._object.isWrapping()) {
if (this._object.isWrapping() && this._pixiObject.width !== 0) {
const alignmentX =
this._object._textAlign === 'right'
? 1

View File

@@ -376,7 +376,7 @@ namespace gdjs {
}
override getWidth(): float {
return this._renderer.getWidth();
return this._wrapping ? this._wrappingWidth : this._renderer.getWidth();
}
override getHeight(): float {

View File

@@ -630,14 +630,16 @@ module.exports = {
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
pixiResourcesLoader,
propertyOverridings
) {
super(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
pixiResourcesLoader,
propertyOverridings
);
// We'll track changes of the font to trigger the loading of the new font.
@@ -663,8 +665,9 @@ module.exports = {
// Update the rendered text properties (note: Pixi is only
// applying changes if there were changed).
const rawText = object.content.text;
this._pixiObject.text = rawText;
this._pixiObject.text = this._propertyOverridings.has('Text')
? this._propertyOverridings.get('Text')
: object.content.text;
const align = object.content.align;
this._pixiObject.align = align;
@@ -718,7 +721,7 @@ module.exports = {
this._pixiObject.dirty = true;
}
if (this._instance.hasCustomSize()) {
if (this._instance.hasCustomSize() && this.getDefaultWidth() !== 0) {
const alignmentX =
object.content.align === 'right'
? 1
@@ -727,17 +730,16 @@ module.exports = {
: 0;
const width = this.getCustomWidth();
const renderedWidth = this.getDefaultWidth();
// A vector from the custom size center to the renderer center.
const centerToCenterX =
(width - this._pixiObject.width) * (alignmentX - 0.5);
const centerToCenterX = (width - renderedWidth) * (alignmentX - 0.5);
this._pixiObject.position.x = this._instance.getX() + width / 2;
this._pixiObject.anchor.x =
0.5 - centerToCenterX / this._pixiObject.width;
this._pixiObject.anchor.x = 0.5 - centerToCenterX / renderedWidth;
} else {
this._pixiObject.position.x =
this._instance.getX() + this._pixiObject.width / 2;
this._instance.getX() + this.getDefaultWidth() / 2;
this._pixiObject.anchor.x = 0.5;
}
const alignmentY =
@@ -747,7 +749,7 @@ module.exports = {
? 0.5
: 0;
this._pixiObject.position.y =
this._instance.getY() + this._pixiObject.height * (0.5 - alignmentY);
this._instance.getY() + this.getDefaultHeight() * (0.5 - alignmentY);
this._pixiObject.anchor.y = 0.5;
this._pixiObject.rotation = RenderedInstance.toRad(
@@ -771,11 +773,11 @@ module.exports = {
}
getDefaultWidth() {
return this._pixiObject.width;
return this._pixiObject.textWidth * this._pixiObject.scale.x;
}
getDefaultHeight() {
return this._pixiObject.height;
return this._pixiObject.textHeight * this._pixiObject.scale.y;
}
getOriginY() {

View File

@@ -146,7 +146,7 @@ namespace gdjs {
}
updatePosition(): void {
if (this._object.isWrapping()) {
if (this._object.isWrapping() && this.getWidth() !== 0) {
const alignmentX =
this._object._textAlign === 'right'
? 1
@@ -155,17 +155,15 @@ namespace gdjs {
: 0;
const width = this._object.getWrappingWidth();
const renderedWidth = this.getWidth();
// A vector from the custom size center to the renderer center.
const centerToCenterX =
(width - this._pixiObject.width) * (alignmentX - 0.5);
const centerToCenterX = (width - renderedWidth) * (alignmentX - 0.5);
this._pixiObject.position.x = this._object.x + width / 2;
this._pixiObject.anchor.x =
0.5 - centerToCenterX / this._pixiObject.width;
this._pixiObject.anchor.x = 0.5 - centerToCenterX / renderedWidth;
} else {
this._pixiObject.position.x =
this._object.x + this._pixiObject.width / 2;
this._pixiObject.position.x = this._object.x + this.getWidth() / 2;
this._pixiObject.anchor.x = 0.5;
}
@@ -176,7 +174,7 @@ namespace gdjs {
? 0.5
: 0;
this._pixiObject.position.y =
this._object.y + this._pixiObject.height * (0.5 - alignmentY);
this._object.y + this.getHeight() * (0.5 - alignmentY);
this._pixiObject.anchor.y = 0.5;
}

View File

@@ -419,7 +419,7 @@ namespace gdjs {
}
override getWidth(): float {
return this._renderer.getWidth();
return this._wrapping ? this._wrappingWidth : this._renderer.getWidth();
}
override getHeight(): float {

View File

@@ -50,6 +50,11 @@ describeIfOnline('Firebase extension end-to-end tests', function () {
.replace('.', '-')}-${Date.now()}`;
before(async function setupFirebase() {
// Delete any existing Firebase app before setup
if (firebase.apps.length !== 0) {
await firebase.app().delete();
}
await gdjs.evtTools.firebaseTools._setupFirebase({
getGame: () => ({
getExtensionProperty: () => JSON.stringify(firebaseConfig),

View File

@@ -15,6 +15,7 @@ class RenderedInstance {
_pixiContainer: PIXI.Container;
_pixiResourcesLoader: Class<PixiResourcesLoader>;
_pixiObject: PIXI.DisplayObject | null;
_propertyOverridings: Map<string, string>;
wasUsed: boolean;
/** Set to true when onRemovedFromScene is called. Allows to cancel promises/asynchronous operations (notably: waiting for a resource load). */
@@ -25,7 +26,8 @@ class RenderedInstance {
instance: gdInitialInstance,
associatedObjectConfiguration: gdObjectConfiguration,
pixiContainer: PIXI.Container,
pixiResourcesLoader: Class<PixiResourcesLoader>
pixiResourcesLoader: Class<PixiResourcesLoader>,
propertyOverridings: Map<string, string> = new Map<string, string>()
);
/**

View File

@@ -917,7 +917,7 @@ module.exports = {
'number',
'LinearVelocityX',
_('Linear velocity X'),
_('the object linear velocity on X.'),
_('the object linear velocity on X'),
_('the linear velocity on X'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -938,7 +938,7 @@ module.exports = {
'number',
'LinearVelocityY',
_('Linear velocity Y'),
_('the object linear velocity on Y.'),
_('the object linear velocity on Y'),
_('the linear velocity on Y'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -959,7 +959,7 @@ module.exports = {
'number',
'LinearVelocityZ',
_('Linear velocity Z'),
_('the object linear velocity on Z.'),
_('the object linear velocity on Z'),
_('the linear velocity on Z'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -980,7 +980,7 @@ module.exports = {
'number',
'LinearVelocityLength',
_('Linear velocity'),
_('the object linear velocity length.'),
_('the object linear velocity length'),
_('the linear velocity length'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -1000,7 +1000,7 @@ module.exports = {
'number',
'AngularVelocityX',
_('Angular velocity X'),
_('the object angular velocity around X.'),
_('the object angular velocity around X'),
_('the angular velocity around X'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -1021,7 +1021,7 @@ module.exports = {
'number',
'AngularVelocityY',
_('Angular velocity Y'),
_('the object angular velocity around Y.'),
_('the object angular velocity around Y'),
_('the angular velocity around Y'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -1042,7 +1042,7 @@ module.exports = {
'number',
'AngularVelocityZ',
_('Angular velocity Z'),
_('the object angular velocity around Z.'),
_('the object angular velocity around Z'),
_('the angular velocity around Z'),
_('Velocity'),
'JsPlatform/Extensions/physics3d.svg'
@@ -1569,7 +1569,7 @@ module.exports = {
.getOrCreate('physics3D')
.setValue(behaviorContent.getChild('physics3D').getStringValue())
.setType('Behavior')
.setLabel('3D capability')
.setLabel('3D physics')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.addExtraInfo('Physics3D::Physics3DBehavior');
@@ -2144,7 +2144,7 @@ module.exports = {
'number',
'ForwardAcceleration',
_('Forward acceleration'),
_('the forward acceleration of an object.'),
_('the forward acceleration of an object'),
_('the forward acceleration'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2166,7 +2166,7 @@ module.exports = {
'number',
'ForwardDeceleration',
_('Forward deceleration'),
_('the forward deceleration of an object.'),
_('the forward deceleration of an object'),
_('the forward deceleration'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2188,7 +2188,7 @@ module.exports = {
'number',
'ForwardSpeedMax',
_('Forward max speed'),
_('the forward max speed of the object.'),
_('the forward max speed of the object'),
_('the forward max speed'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2232,7 +2232,7 @@ module.exports = {
'number',
'SidewaysAcceleration',
_('Sideways acceleration'),
_('the sideways acceleration of an object.'),
_('the sideways acceleration of an object'),
_('the sideways acceleration'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2254,7 +2254,7 @@ module.exports = {
'number',
'SidewaysDeceleration',
_('Sideways deceleration'),
_('the sideways deceleration of an object.'),
_('the sideways deceleration of an object'),
_('the sideways deceleration'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2276,7 +2276,7 @@ module.exports = {
'number',
'SidewaysSpeedMax',
_('Sideways max speed'),
_('the sideways max speed of the object.'),
_('the sideways max speed of the object'),
_('the sideways max speed'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2345,7 +2345,7 @@ module.exports = {
'number',
'JumpSpeed',
_('Jump speed'),
_('the jump speed of an object. Its value is always positive.'),
_('the jump speed of an object. Its value is always positive'),
_('the jump speed'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2389,7 +2389,7 @@ module.exports = {
'number',
'Gravity',
_('Gravity'),
_('the gravity applied on an object.'),
_('the gravity applied on an object'),
_('the gravity'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'
@@ -2411,7 +2411,7 @@ module.exports = {
'number',
'FallingSpeedMax',
_('Maximum falling speed'),
_('the maximum falling speed of an object.'),
_('the maximum falling speed of an object'),
_('the maximum falling speed'),
_('Character configuration'),
'JsPlatform/Extensions/physics_character3d.svg'

View File

@@ -283,11 +283,11 @@ namespace gdjs {
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
const physics3DSharedData = runtimeScene.physics3DSharedData;
if (physics3DSharedData) {
Jolt.destroy(physics3DSharedData.jolt);
Jolt.destroy(physics3DSharedData.contactListener);
Jolt.destroy(physics3DSharedData._tempVec3);
Jolt.destroy(physics3DSharedData._tempRVec3);
Jolt.destroy(physics3DSharedData._tempQuat);
Jolt.destroy(physics3DSharedData.jolt);
runtimeScene.physics3DSharedData = null;
}
});
@@ -302,9 +302,9 @@ namespace gdjs {
fixedRotation: boolean;
private shape: string;
private shapeOrientation: string;
private shapeDimensionA: any;
private shapeDimensionB: any;
private shapeDimensionC: any;
private shapeDimensionA: float;
private shapeDimensionB: float;
private shapeDimensionC: float;
private density: float;
friction: float;
restitution: float;
@@ -387,7 +387,8 @@ namespace gdjs {
this.bullet = behaviorData.bullet;
this.fixedRotation = behaviorData.fixedRotation;
this.shape = behaviorData.shape;
this.shapeOrientation = behaviorData.shapeOrientation;
this.shapeOrientation =
behaviorData.shape === 'Box' ? 'Z' : behaviorData.shapeOrientation;
this.shapeDimensionA = behaviorData.shapeDimensionA;
this.shapeDimensionB = behaviorData.shapeDimensionB;
this.shapeDimensionC = behaviorData.shapeDimensionC;
@@ -424,7 +425,7 @@ namespace gdjs {
return tempQuat;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
if (oldBehaviorData.bullet !== newBehaviorData.bullet) {
this.setBullet(newBehaviorData.bullet);
}
@@ -477,7 +478,7 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Physics3DNetworkSyncData {
override getNetworkSyncData(): Physics3DNetworkSyncData {
let bodyProps;
if (this._body) {
const position = this._body.GetPosition();
@@ -528,7 +529,9 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: Physics3DNetworkSyncData) {
override updateFromNetworkSyncData(
networkSyncData: Physics3DNetworkSyncData
) {
super.updateFromNetworkSyncData(networkSyncData);
const behaviorSpecificProps = networkSyncData.props;
@@ -608,7 +611,7 @@ namespace gdjs {
}
}
onDeActivate() {
override onDeActivate() {
this._sharedData.removeFromBehaviorsList(this);
this.bodyUpdater.destroyBody();
this._contactsEndedThisFrame.length = 0;
@@ -616,7 +619,7 @@ namespace gdjs {
this._currentContacts.length = 0;
}
onActivate() {
override onActivate() {
this._sharedData.addToBehaviorsList(this);
this._contactsEndedThisFrame.length = 0;
@@ -625,7 +628,7 @@ namespace gdjs {
this.updateBodyFromObject();
}
onDestroy() {
override onDestroy() {
this._destroyedDuringFrameLogic = true;
this.onDeActivate();
}
@@ -728,15 +731,13 @@ namespace gdjs {
this._shapeHalfDepth = radius;
}
shapeSettings.mDensity = this.density;
const rotatedShape = new Jolt.RotatedTranslatedShapeSettings(
const rotatedShapeSettings = new Jolt.RotatedTranslatedShapeSettings(
this.getVec3(0, 0, 0),
quat,
shapeSettings
)
.Create()
.Get();
Jolt.destroy(shapeSettings);
);
const rotatedShape = rotatedShapeSettings.Create().Get();
Jolt.destroy(rotatedShapeSettings);
return rotatedShape;
}
@@ -822,7 +823,7 @@ namespace gdjs {
: this.masks;
}
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
override doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Step the world if not done this frame yet.
// Don't step at the first frame to allow events to handle overlapping objects.
if (
@@ -836,7 +837,9 @@ namespace gdjs {
}
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
override doStepPostEvents(
instanceContainer: gdjs.RuntimeInstanceContainer
) {
// Reset world step to update next frame
this._sharedData.stepped = false;
}
@@ -1467,11 +1470,11 @@ namespace gdjs {
const deltaX = towardX - body.GetPosition().GetX();
const deltaY = towardY - body.GetPosition().GetY();
const deltaZ = towardZ - body.GetPosition().GetZ();
const distance = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
if (distance === 0) {
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
if (distanceSq === 0) {
return;
}
const ratio = length / distance;
const ratio = length / Math.sqrt(distanceSq);
this._sharedData.bodyInterface.AddForce(
body.GetID(),
@@ -1537,11 +1540,11 @@ namespace gdjs {
const deltaX = towardX - originX;
const deltaY = towardY - originY;
const deltaZ = towardZ - originZ;
const distance = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
if (distance === 0) {
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
if (distanceSq === 0) {
return;
}
const ratio = length / distance;
const ratio = length / Math.sqrt(distanceSq);
this._sharedData.bodyInterface.AddImpulse(
body.GetID(),
@@ -1765,7 +1768,9 @@ namespace gdjs {
destroyBody(): void;
}
export class DefaultBodyUpdater {
export class DefaultBodyUpdater
implements gdjs.Physics3DRuntimeBehavior.BodyUpdater
{
behavior: gdjs.Physics3DRuntimeBehavior;
constructor(behavior: gdjs.Physics3DRuntimeBehavior) {

View File

@@ -1,11 +1,5 @@
/// <reference path="./jolt-physics.d.ts" />
namespace Jolt {
export interface Body {
gdjsAssociatedCharacterBehavior: gdjs.PhysicsCharacter3DRuntimeBehavior | null;
}
}
namespace gdjs {
interface PhysicsCharacter3DNetworkSyncDataType {
fwa: float;
@@ -215,7 +209,7 @@ namespace gdjs {
return this._physics3D;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
if (oldBehaviorData.gravity !== newBehaviorData.gravity) {
this.setGravity(newBehaviorData.gravity);
}
@@ -274,7 +268,7 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): PhysicsCharacter3DNetworkSyncData {
override getNetworkSyncData(): PhysicsCharacter3DNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
@@ -302,7 +296,7 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(
override updateFromNetworkSyncData(
networkSyncData: PhysicsCharacter3DNetworkSyncData
) {
super.updateFromNetworkSyncData(networkSyncData);
@@ -395,13 +389,13 @@ namespace gdjs {
this.owner3D.setAngle(gdjs.toDegrees(euler.z));
}
onDeActivate() {
override onDeActivate() {
this.collisionChecker.clearContacts();
}
onActivate() {}
override onActivate() {}
onDestroy() {
override onDestroy() {
this._destroyedDuringFrameLogic = true;
this.onDeActivate();
this._destroyCharacter();
@@ -420,6 +414,7 @@ namespace gdjs {
if (this.character) {
if (this._canBePushed) {
this.charactersManager.removeCharacter(this.character);
Jolt.destroy(this.character.GetListener());
}
// The body is destroyed with the character.
Jolt.destroy(this.character);
@@ -443,7 +438,14 @@ namespace gdjs {
}
}
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
override doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Trigger createAndAddBody()
this.getPhysics3D();
}
override doStepPostEvents(
instanceContainer: gdjs.RuntimeInstanceContainer
) {
// Trigger createAndAddBody()
this.getPhysics3D();
}
@@ -625,7 +627,7 @@ namespace gdjs {
this._wasRightKeyPressed = this._hasPressedRightKey;
this._wasLeftKeyPressed = this._hasPressedLeftKey;
this._wasJumpKeyPressed = this._hasPressedJumpKey;
this._wasStickUsed = this._hasPressedJumpKey;
this._wasStickUsed = this._hasUsedStick;
if (!this._dontClearInputsBetweenFrames) {
this._hasPressedForwardKey = false;
@@ -846,8 +848,6 @@ namespace gdjs {
return shouldFollow;
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
onObjectHotReloaded() {}
/**
@@ -1486,7 +1486,9 @@ namespace gdjs {
).destroy();
});
export class CharacterBodyUpdater {
export class CharacterBodyUpdater
implements gdjs.Physics3DRuntimeBehavior.BodyUpdater
{
characterBehavior: gdjs.PhysicsCharacter3DRuntimeBehavior;
constructor(characterBehavior: gdjs.PhysicsCharacter3DRuntimeBehavior) {
@@ -1546,9 +1548,20 @@ namespace gdjs {
behavior.getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
_sharedData.physicsSystem
);
Jolt.destroy(settings);
const body = _sharedData.physicsSystem
.GetBodyLockInterface()
.TryGetBody(character.GetInnerBodyID());
if (this.characterBehavior.character) {
if (this.characterBehavior._canBePushed) {
this.characterBehavior.charactersManager.removeCharacter(
this.characterBehavior.character
);
// Character.mListener is a plain pointer, it's not destroyed with the character.
Jolt.destroy(this.characterBehavior.character.GetListener());
}
Jolt.destroy(this.characterBehavior.character);
}
this.characterBehavior.character = character;
if (this.characterBehavior._canBePushed) {

View File

@@ -23,7 +23,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"held, customizable gravity... It can be used for the player, but "
"also for other objects moving on platforms. In this case though, "
"it's recommended to first check if there is a simpler behavior that "
"could be used.",
"could be used. Default controls for keyboards are included. For "
"touch or gamepads, use the \"Multitouch Joystick\" objects and the "
"associated \"mapper\" behaviors.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
@@ -33,16 +35,16 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.SetIcon("CppPlatform/Extensions/platformerobjecticon.png");
{
gd::BehaviorMetadata& aut = extension.AddBehavior(
"PlatformerObjectBehavior",
_("Platformer character"),
"PlatformerObject",
_("Jump and run on platforms."),
"",
"CppPlatform/Extensions/platformerobjecticon.png",
"PlatformerObjectBehavior",
std::make_shared<PlatformerObjectBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
gd::BehaviorMetadata& aut =
extension.AddBehavior("PlatformerObjectBehavior",
_("Platformer character"),
"PlatformerObject",
_("Jump and run on platforms."),
"",
"CppPlatform/Extensions/platformerobjecticon.png",
"PlatformerObjectBehavior",
std::make_shared<PlatformerObjectBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
// Deprecated, use IsMovingEvenALittle instead
aut.AddCondition("IsMoving",
@@ -59,14 +61,15 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsSimple()
.SetFunctionName("IsMoving");
aut.AddScopedCondition("IsMovingEvenALittle",
_("Is moving"),
_("Check if the object is moving (whether it is on the "
"floor or in the air)."),
_("_PARAM0_ is moving"),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
aut.AddScopedCondition(
"IsMovingEvenALittle",
_("Is moving"),
_("Check if the object is moving (whether it is on the "
"floor or in the air)."),
_("_PARAM0_ is moving"),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.MarkAsSimple();
@@ -525,14 +528,14 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsAdvanced()
.SetFunctionName("SimulateLadderKey");
aut.AddAction(
"SimulateReleaseLadderKey",
_("Simulate release ladder key press"),
_("Simulate a press of the Release Ladder key (used to get off a ladder)."),
_("Simulate pressing Release Ladder key for _PARAM0_"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
aut.AddAction("SimulateReleaseLadderKey",
_("Simulate release ladder key press"),
_("Simulate a press of the Release Ladder key (used to get "
"off a ladder)."),
_("Simulate pressing Release Ladder key for _PARAM0_"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.MarkAsAdvanced();
@@ -550,7 +553,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddAction("SimulateReleasePlatformKey",
_("Simulate release platform key press"),
_("Simulate a press of the release platform key (used when grabbing a "
_("Simulate a press of the release platform key (used when "
"grabbing a "
"platform ledge)."),
_("Simulate pressing Release Platform key for _PARAM0_"),
_("Platformer controls"),
@@ -561,7 +565,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SimulateReleasePlatformKey");
// Support for deprecated names:
aut.AddDuplicatedAction("SimulateReleaseKey", "SimulateReleasePlatformKey").SetHidden();
aut.AddDuplicatedAction("SimulateReleaseKey", "SimulateReleasePlatformKey")
.SetHidden();
aut.AddAction("SimulateControl",
_("Simulate control"),
@@ -574,23 +579,27 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.AddParameter("stringWithSelector",
_("Key"),
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
_("Key"),
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release "
"Ladder\", \"Up\", \"Down\"]")
.MarkAsAdvanced()
.SetFunctionName("SimulateControl");
aut.AddScopedCondition("IsUsingControl",
_("Control pressed or simulated"),
_("A control was applied from a default control or simulated by an action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_("Platformer state"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
aut.AddScopedCondition(
"IsUsingControl",
_("Control pressed or simulated"),
_("A control was applied from a default control or simulated by an "
"action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_("Platformer state"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.AddParameter("stringWithSelector",
_("Key"),
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
_("Key"),
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release "
"Ladder\", \"Up\", \"Down\"]")
.MarkAsAdvanced();
aut.AddAction("IgnoreDefaultControls",
@@ -792,59 +801,65 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.SetFunctionName("GetJumpSpeed");
aut.AddExpression("JumpSustainTime",
_("Jump sustain time"),
_("Return the jump sustain time of the object (in seconds)."
"This is the time during which keeping the jump button held "
"allow the initial jump speed to be maintained."),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
aut.AddExpression(
"JumpSustainTime",
_("Jump sustain time"),
_("Return the jump sustain time of the object (in seconds)."
"This is the time during which keeping the jump button held "
"allow the initial jump speed to be maintained."),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior");
aut.AddExpression("CurrentFallSpeed",
_("Current fall speed"),
_("Return the current fall speed of the object "
"(in pixels per second). Its value is always positive."),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
aut.AddExpression(
"CurrentFallSpeed",
_("Current fall speed"),
_("Return the current fall speed of the object "
"(in pixels per second). Its value is always positive."),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.SetFunctionName("GetCurrentFallSpeed");
aut.AddExpression("CurrentSpeed",
_("Current horizontal speed"),
_("Return the current horizontal speed of the object "
"(in pixels per second). The object moves to the left "
"with negative values and to the right with positive ones"),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
aut.AddExpression(
"CurrentSpeed",
_("Current horizontal speed"),
_("Return the current horizontal speed of the object "
"(in pixels per second). The object moves to the left "
"with negative values and to the right with positive ones"),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.SetFunctionName("GetCurrentSpeed");
aut.AddExpression("CurrentJumpSpeed",
_("Current jump speed"),
_("Return the current jump speed of the object "
"(in pixels per second). Its value is always positive."),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
aut.AddExpression(
"CurrentJumpSpeed",
_("Current jump speed"),
_("Return the current jump speed of the object "
"(in pixels per second). Its value is always positive."),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.SetFunctionName("GetCurrentJumpSpeed");
}
{
gd::BehaviorMetadata& aut = extension.AddBehavior(
"PlatformBehavior",
_("Platform"),
"Platform",
_("Flag objects as being platforms which characters can run on."),
"",
"CppPlatform/Extensions/platformicon.png",
"PlatformBehavior",
std::make_shared<PlatformBehavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
gd::BehaviorMetadata& aut =
extension
.AddBehavior("PlatformBehavior",
_("Platform"),
"Platform",
_("Flag objects as being platforms which characters "
"can run on."),
"",
"CppPlatform/Extensions/platformicon.png",
"PlatformBehavior",
std::make_shared<PlatformBehavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
aut.AddAction("ChangePlatformType",
_("Platform type"),
@@ -857,21 +872,23 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformBehavior")
.AddParameter("stringWithSelector",
_("Platform type"),
"[\"Platform\",\"Jumpthru\",\"Ladder\"]")
_("Platform type"),
"[\"Platform\",\"Jumpthru\",\"Ladder\"]")
.MarkAsAdvanced()
.SetFunctionName("ChangePlatformType");
}
extension.AddCondition("IsObjectOnGivenFloor",
_("Character is on given platform"),
_("Check if a platformer character is on a given platform."),
_("_PARAM0_ is on platform _PARAM2_"),
_("Collision"),
"CppPlatform/Extensions/platformicon.png",
"CppPlatform/Extensions/platformicon.png")
.AddParameter("objectList", _("Object"), "", false)
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.AddParameter("objectList", _("Platforms"), "", false)
.AddCodeOnlyParameter("conditionInverted", "");
extension
.AddCondition(
"IsObjectOnGivenFloor",
_("Character is on given platform"),
_("Check if a platformer character is on a given platform."),
_("_PARAM0_ is on platform _PARAM2_"),
_("Collision"),
"CppPlatform/Extensions/platformicon.png",
"CppPlatform/Extensions/platformicon.png")
.AddParameter("objectList", _("Object"), "", false)
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.AddParameter("objectList", _("Platforms"), "", false)
.AddCodeOnlyParameter("conditionInverted", "");
}

View File

@@ -98,7 +98,7 @@ namespace gdjs {
}
updatePosition(): void {
if (this._object.isWrapping()) {
if (this._object.isWrapping() && this._text.width !== 0) {
const alignmentX =
this._object._textAlign === 'right'
? 1
@@ -117,7 +117,6 @@ namespace gdjs {
this._text.position.x = this._object.x + this._text.width / 2;
this._text.anchor.x = 0.5;
}
this._text.position.y = this._object.y + this._text.height / 2;
const alignmentY =
this._object._verticalTextAlignment === 'bottom'

View File

@@ -12,6 +12,7 @@ namespace gdjs {
export type CustomObjectConfiguration = ObjectConfiguration & {
animatable?: SpriteAnimationData[];
variant: string;
childrenContent: { [objectName: string]: ObjectConfiguration & any };
};
@@ -92,34 +93,57 @@ namespace gdjs {
}
private _initializeFromObjectData(
objectData: ObjectData & CustomObjectConfiguration
customObjectData: ObjectData & CustomObjectConfiguration
) {
const eventsBasedObjectData = this._runtimeScene
.getGame()
.getEventsBasedObjectData(objectData.type);
.getEventsBasedObjectData(customObjectData.type);
if (!eventsBasedObjectData) {
logger.error(
`A CustomRuntimeObject was initialized (or re-initialized) from object data referring to an non existing events based object data with type "${objectData.type}".`
`A CustomRuntimeObject was initialized (or re-initialized) from object data referring to an non existing events based object data with type "${customObjectData.type}".`
);
return;
}
if (!eventsBasedObjectData.defaultVariant) {
eventsBasedObjectData.defaultVariant = {
...eventsBasedObjectData,
name: '',
};
}
let usedVariantData: EventsBasedObjectVariantData =
eventsBasedObjectData.defaultVariant;
if (customObjectData.variant) {
for (
let variantIndex = 0;
variantIndex < eventsBasedObjectData.variants.length;
variantIndex++
) {
const variantData = eventsBasedObjectData.variants[variantIndex];
if (variantData.name === customObjectData.variant) {
usedVariantData = variantData;
break;
}
}
}
this._isInnerAreaFollowingParentSize =
eventsBasedObjectData.isInnerAreaFollowingParentSize;
if (eventsBasedObjectData.instances.length > 0) {
if (usedVariantData.instances.length > 0) {
if (!this._innerArea) {
this._innerArea = {
min: [0, 0, 0],
max: [0, 0, 0],
};
}
this._innerArea.min[0] = eventsBasedObjectData.areaMinX;
this._innerArea.min[1] = eventsBasedObjectData.areaMinY;
this._innerArea.min[2] = eventsBasedObjectData.areaMinZ;
this._innerArea.max[0] = eventsBasedObjectData.areaMaxX;
this._innerArea.max[1] = eventsBasedObjectData.areaMaxY;
this._innerArea.max[2] = eventsBasedObjectData.areaMaxZ;
this._innerArea.min[0] = usedVariantData.areaMinX;
this._innerArea.min[1] = usedVariantData.areaMinY;
this._innerArea.min[2] = usedVariantData.areaMinZ;
this._innerArea.max[0] = usedVariantData.areaMaxX;
this._innerArea.max[1] = usedVariantData.areaMaxY;
this._innerArea.max[2] = usedVariantData.areaMaxZ;
}
this._instanceContainer.loadFrom(objectData, eventsBasedObjectData);
this._instanceContainer.loadFrom(customObjectData, usedVariantData);
}
protected abstract _createRender():

View File

@@ -65,22 +65,26 @@ namespace gdjs {
*/
loadFrom(
customObjectData: ObjectData & CustomObjectConfiguration,
eventsBasedObjectData: EventsBasedObjectData
eventsBasedObjectVariantData: EventsBasedObjectVariantData
) {
if (this._isLoaded) {
this.onDestroyFromScene(this._parent);
}
this._setOriginalInnerArea(eventsBasedObjectData);
this._setOriginalInnerArea(eventsBasedObjectVariantData);
// Registering objects
for (
let i = 0, len = eventsBasedObjectData.objects.length;
let i = 0, len = eventsBasedObjectVariantData.objects.length;
i < len;
++i
) {
const childObjectData = eventsBasedObjectData.objects[i];
if (customObjectData.childrenContent) {
const childObjectData = eventsBasedObjectVariantData.objects[i];
// The children configuration override only applies to the default variant.
if (
customObjectData.childrenContent &&
!eventsBasedObjectVariantData.name
) {
this.registerObject({
...childObjectData,
// The custom object overrides its events-based object configuration.
@@ -92,14 +96,14 @@ namespace gdjs {
}
}
if (eventsBasedObjectData.layers.length > 0) {
if (eventsBasedObjectVariantData.layers.length > 0) {
// Load layers
for (
let i = 0, len = eventsBasedObjectData.layers.length;
let i = 0, len = eventsBasedObjectVariantData.layers.length;
i < len;
++i
) {
this.addLayer(eventsBasedObjectData.layers[i]);
this.addLayer(eventsBasedObjectVariantData.layers[i]);
}
} else {
// Add a default layer
@@ -128,7 +132,7 @@ namespace gdjs {
}
this.createObjectsFrom(
eventsBasedObjectData.instances,
eventsBasedObjectVariantData.instances,
0,
0,
0,
@@ -147,7 +151,7 @@ namespace gdjs {
* `_initialInnerArea` is shared by every instance to save memory.
*/
private _setOriginalInnerArea(
eventsBasedObjectData: EventsBasedObjectData
eventsBasedObjectData: EventsBasedObjectVariantData
) {
if (eventsBasedObjectData.instances.length > 0) {
if (!eventsBasedObjectData._initialInnerArea) {

View File

@@ -206,9 +206,19 @@ declare interface SceneAndExtensionsData {
usedExtensionsWithVariablesData: EventsFunctionsExtensionData[];
}
declare interface EventsBasedObjectData extends InstanceContainerData {
declare interface EventsBasedObjectData
extends EventsBasedObjectVariantData,
InstanceContainerData {
name: string;
isInnerAreaFollowingParentSize: boolean;
variants: Array<EventsBasedObjectVariantData>;
/** Added at runtime to have the default variant with an empty name instead
* of the events-based object name. */
defaultVariant?: EventsBasedObjectVariantData;
}
declare interface EventsBasedObjectVariantData extends InstanceContainerData {
name: string;
// The flat representation of defaultSize.
areaMinX: float;
areaMinY: float;
@@ -225,6 +235,9 @@ declare interface EventsBasedObjectData extends InstanceContainerData {
min: [float, float, float];
max: [float, float, float];
} | null;
instances: InstanceData[];
objects: ObjectData[];
layers: LayerData[];
}
declare interface BehaviorSharedData {

View File

@@ -15,6 +15,7 @@ describe('gdjs.CustomRuntimeObject', function () {
const customObject = new gdjs.CustomRuntimeObject2D(instanceContainer, {
name: 'MyCustomObject',
type: 'MyExtension::MyEventsBasedObject',
variant: '',
variables: [],
behaviors: [],
effects: [],

View File

@@ -93,6 +93,7 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
/** @type {ObjectData & gdjs.CustomObjectConfiguration} */
const defaultCustomObject = {
type: 'MyExtension::MyCustomObject',
variant: '',
name: 'MyCustomObject',
behaviors: [],
variables: [],
@@ -214,6 +215,7 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
areaMaxZ: 0,
_initialInnerArea: null,
isInnerAreaFollowingParentSize: false,
variants: [],
};
};

View File

@@ -372,6 +372,16 @@ interface ObjectVariableHelper {
[Ref] ObjectsContainer globalObjectsContainer,
[Ref] ObjectsContainer objectsContainer,
[Const, Ref] ObjectGroup objectGroup);
void STATIC_ApplyChangesToVariants(
[Ref] EventsBasedObject eventsBasedObject,
[Const] DOMString objectName,
[Const, Ref] VariablesChangeset changeset);
};
interface EventsBasedObjectVariantHelper {
void STATIC_ComplyVariantsToEventsBasedObject(
[Ref, Const] Project project,
[Ref] EventsBasedObject eventsBasedObject);
};
interface ObjectGroupsContainer {
@@ -923,6 +933,8 @@ enum CustomObjectConfiguration_EdgeAnchor {
interface CustomObjectConfiguration {
[Value] UniquePtrObjectConfiguration Clone();
[Const, Ref] DOMString GetVariantName();
void SetVariantName([Const] DOMString name);
boolean IsForcedToOverrideEventsBasedObjectChildrenConfiguration();
boolean IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration();
void SetMarkedAsOverridingEventsBasedObjectChildrenConfiguration(
@@ -3146,6 +3158,9 @@ interface EventsBasedObject {
[Ref] EventsBasedObject MakAsUsingLegacyInstancesRenderer(boolean value);
boolean IsUsingLegacyInstancesRenderer();
[Ref] EventsBasedObjectVariant GetDefaultVariant();
[Ref] EventsBasedObjectVariantsContainer GetVariants();
[Ref] InitialInstancesContainer GetInitialInstances();
[Ref] LayersContainer GetLayers();
[Ref] ObjectsContainer GetObjects();
@@ -3169,6 +3184,48 @@ interface EventsBasedObject {
};
EventsBasedObject implements AbstractEventsBasedEntity;
interface EventsBasedObjectVariant {
void EventsBasedObjectVariant();
[Const, Ref] DOMString GetName();
[Ref] EventsBasedObjectVariant SetName([Const] DOMString name);
[Ref] InitialInstancesContainer GetInitialInstances();
[Ref] LayersContainer GetLayers();
[Ref] ObjectsContainer GetObjects();
double GetAreaMinX();
double GetAreaMinY();
double GetAreaMinZ();
double GetAreaMaxX();
double GetAreaMaxY();
double GetAreaMaxZ();
void SetAreaMinX(double value);
void SetAreaMinY(double value);
void SetAreaMinZ(double value);
void SetAreaMaxX(double value);
void SetAreaMaxY(double value);
void SetAreaMaxZ(double value);
void SetAssetStoreAssetId([Const] DOMString assetStoreAssetId);
[Const, Ref] DOMString GetAssetStoreAssetId();
void SetAssetStoreOriginalName([Const] DOMString assetStoreOriginalName);
[Const, Ref] DOMString GetAssetStoreOriginalName();
void SerializeTo([Ref] SerializerElement element);
void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element);
};
interface EventsBasedObjectVariantsContainer {
[Ref] EventsBasedObjectVariant InsertNewVariant([Const] DOMString name, unsigned long pos);
[Ref] EventsBasedObjectVariant InsertVariant([Const, Ref] EventsBasedObjectVariant variant, unsigned long pos);
boolean HasVariantNamed([Const] DOMString name);
[Ref] EventsBasedObjectVariant GetVariant([Const] DOMString name);
[Ref] EventsBasedObjectVariant GetVariantAt(unsigned long pos);
void RemoveVariant([Const] DOMString name);
void MoveVariant(unsigned long oldIndex, unsigned long newIndex);
unsigned long GetVariantsCount();
unsigned long GetVariantPosition([Const, Ref] EventsBasedObjectVariant variant);
};
interface EventsBasedObjectsList {
[Ref] EventsBasedObject InsertNew([Const] DOMString name, unsigned long pos);
[Ref] EventsBasedObject Insert([Const, Ref] EventsBasedObject item, unsigned long pos);
@@ -3249,6 +3306,7 @@ interface EventsFunctionsExtension {
[Ref] EventsBasedObjectsList GetEventsBasedObjects();
void SerializeTo([Ref] SerializerElement element);
void SerializeToExternal([Ref] SerializerElement element);
void UnserializeFrom([Ref] Project project, [Const, Ref] SerializerElement element);
boolean STATIC_IsExtensionLifecycleEventsFunction([Const] DOMString eventsFunctionName);

View File

@@ -47,6 +47,7 @@
#include <GDCore/IDE/Events/ExampleExtensionUsagesFinder.h>
#include <GDCore/IDE/EventsFunctionTools.h>
#include <GDCore/IDE/ObjectVariableHelper.h>
#include <GDCore/IDE/EventsBasedObjectVariantHelper.h>
#include <GDCore/IDE/Project/ArbitraryResourceWorker.h>
#include <GDCore/IDE/Project/ArbitraryObjectsWorker.h>
#include <GDCore/IDE/Project/ObjectsUsingResourceCollector.h>
@@ -727,6 +728,8 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
ComputeChangesetForVariablesContainer
#define STATIC_MergeVariableContainers MergeVariableContainers
#define STATIC_FillAnyVariableBetweenObjects FillAnyVariableBetweenObjects
#define STATIC_ApplyChangesToVariants ApplyChangesToVariants
#define STATIC_ComplyVariantsToEventsBasedObject ComplyVariantsToEventsBasedObject
#define STATIC_RenameEventsFunctionsExtension RenameEventsFunctionsExtension
#define STATIC_UpdateExtensionNameInEventsBasedBehavior \
UpdateExtensionNameInEventsBasedBehavior
@@ -846,6 +849,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
#define RemoveEventAt RemoveEvent
#define RemoveAt Remove
#define GetEventsFunctionAt GetEventsFunction
#define GetVariantAt GetVariant
#define GetEffectAt GetEffect
#define GetParameterAt GetParameter

View File

@@ -384,6 +384,11 @@ export class ObjectGroup extends EmscriptenObject {
export class ObjectVariableHelper extends EmscriptenObject {
static mergeVariableContainers(objectsContainersList: ObjectsContainersList, objectGroup: ObjectGroup): VariablesContainer;
static fillAnyVariableBetweenObjects(globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, objectGroup: ObjectGroup): void;
static applyChangesToVariants(eventsBasedObject: EventsBasedObject, objectName: string, changeset: VariablesChangeset): void;
}
export class EventsBasedObjectVariantHelper extends EmscriptenObject {
static complyVariantsToEventsBasedObject(project: Project, eventsBasedObject: EventsBasedObject): void;
}
export class ObjectGroupsContainer extends EmscriptenObject {
@@ -768,6 +773,8 @@ export class ObjectJsImplementation extends ObjectConfiguration {
export class CustomObjectConfiguration extends ObjectConfiguration {
clone(): UniquePtrObjectConfiguration;
getVariantName(): string;
setVariantName(name: string): void;
isForcedToOverrideEventsBasedObjectChildrenConfiguration(): boolean;
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration(): boolean;
setMarkedAsOverridingEventsBasedObjectChildrenConfiguration(isOverridingEventsBasedObjectChildrenConfiguration: boolean): void;
@@ -2273,6 +2280,8 @@ export class EventsBasedObject extends AbstractEventsBasedEntity {
isInnerAreaFollowingParentSize(): boolean;
makAsUsingLegacyInstancesRenderer(value: boolean): EventsBasedObject;
isUsingLegacyInstancesRenderer(): boolean;
getDefaultVariant(): EventsBasedObjectVariant;
getVariants(): EventsBasedObjectVariantsContainer;
getInitialInstances(): InitialInstancesContainer;
getLayers(): LayersContainer;
getObjects(): ObjectsContainer;
@@ -2294,6 +2303,45 @@ export class EventsBasedObject extends AbstractEventsBasedEntity {
static getPropertyToggleActionName(propertyName: string): string;
}
export class EventsBasedObjectVariant extends EmscriptenObject {
constructor();
getName(): string;
setName(name: string): EventsBasedObjectVariant;
getInitialInstances(): InitialInstancesContainer;
getLayers(): LayersContainer;
getObjects(): ObjectsContainer;
getAreaMinX(): number;
getAreaMinY(): number;
getAreaMinZ(): number;
getAreaMaxX(): number;
getAreaMaxY(): number;
getAreaMaxZ(): number;
setAreaMinX(value: number): void;
setAreaMinY(value: number): void;
setAreaMinZ(value: number): void;
setAreaMaxX(value: number): void;
setAreaMaxY(value: number): void;
setAreaMaxZ(value: number): void;
setAssetStoreAssetId(assetStoreAssetId: string): void;
getAssetStoreAssetId(): string;
setAssetStoreOriginalName(assetStoreOriginalName: string): void;
getAssetStoreOriginalName(): string;
serializeTo(element: SerializerElement): void;
unserializeFrom(project: Project, element: SerializerElement): void;
}
export class EventsBasedObjectVariantsContainer extends EmscriptenObject {
insertNewVariant(name: string, pos: number): EventsBasedObjectVariant;
insertVariant(variant: EventsBasedObjectVariant, pos: number): EventsBasedObjectVariant;
hasVariantNamed(name: string): boolean;
getVariant(name: string): EventsBasedObjectVariant;
getVariantAt(pos: number): EventsBasedObjectVariant;
removeVariant(name: string): void;
moveVariant(oldIndex: number, newIndex: number): void;
getVariantsCount(): number;
getVariantPosition(variant: EventsBasedObjectVariant): number;
}
export class EventsBasedObjectsList extends EmscriptenObject {
insertNew(name: string, pos: number): EventsBasedObject;
insert(item: EventsBasedObject, pos: number): EventsBasedObject;
@@ -2364,6 +2412,7 @@ export class EventsFunctionsExtension extends EmscriptenObject {
getEventsBasedBehaviors(): EventsBasedBehaviorsList;
getEventsBasedObjects(): EventsBasedObjectsList;
serializeTo(element: SerializerElement): void;
serializeToExternal(element: SerializerElement): void;
unserializeFrom(project: Project, element: SerializerElement): void;
static isExtensionLifecycleEventsFunction(eventsFunctionName: string): boolean;
}

View File

@@ -6,6 +6,8 @@ declare class gdCustomObjectConfiguration extends gdObjectConfiguration {
static Proportional: 3;
static Center: 4;
clone(): gdUniquePtrObjectConfiguration;
getVariantName(): string;
setVariantName(name: string): void;
isForcedToOverrideEventsBasedObjectChildrenConfiguration(): boolean;
isMarkedAsOverridingEventsBasedObjectChildrenConfiguration(): boolean;
setMarkedAsOverridingEventsBasedObjectChildrenConfiguration(isOverridingEventsBasedObjectChildrenConfiguration: boolean): void;

View File

@@ -17,6 +17,8 @@ declare class gdEventsBasedObject extends gdAbstractEventsBasedEntity {
isInnerAreaFollowingParentSize(): boolean;
makAsUsingLegacyInstancesRenderer(value: boolean): gdEventsBasedObject;
isUsingLegacyInstancesRenderer(): boolean;
getDefaultVariant(): gdEventsBasedObjectVariant;
getVariants(): gdEventsBasedObjectVariantsContainer;
getInitialInstances(): gdInitialInstancesContainer;
getLayers(): gdLayersContainer;
getObjects(): gdObjectsContainer;

View File

@@ -0,0 +1,29 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdEventsBasedObjectVariant {
constructor(): void;
getName(): string;
setName(name: string): gdEventsBasedObjectVariant;
getInitialInstances(): gdInitialInstancesContainer;
getLayers(): gdLayersContainer;
getObjects(): gdObjectsContainer;
getAreaMinX(): number;
getAreaMinY(): number;
getAreaMinZ(): number;
getAreaMaxX(): number;
getAreaMaxY(): number;
getAreaMaxZ(): number;
setAreaMinX(value: number): void;
setAreaMinY(value: number): void;
setAreaMinZ(value: number): void;
setAreaMaxX(value: number): void;
setAreaMaxY(value: number): void;
setAreaMaxZ(value: number): void;
setAssetStoreAssetId(assetStoreAssetId: string): void;
getAssetStoreAssetId(): string;
setAssetStoreOriginalName(assetStoreOriginalName: string): void;
getAssetStoreOriginalName(): string;
serializeTo(element: gdSerializerElement): void;
unserializeFrom(project: gdProject, element: gdSerializerElement): void;
delete(): void;
ptr: number;
};

View File

@@ -0,0 +1,6 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdEventsBasedObjectVariantHelper {
static complyVariantsToEventsBasedObject(project: gdProject, eventsBasedObject: gdEventsBasedObject): void;
delete(): void;
ptr: number;
};

View File

@@ -0,0 +1,14 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdEventsBasedObjectVariantsContainer {
insertNewVariant(name: string, pos: number): gdEventsBasedObjectVariant;
insertVariant(variant: gdEventsBasedObjectVariant, pos: number): gdEventsBasedObjectVariant;
hasVariantNamed(name: string): boolean;
getVariant(name: string): gdEventsBasedObjectVariant;
getVariantAt(pos: number): gdEventsBasedObjectVariant;
removeVariant(name: string): void;
moveVariant(oldIndex: number, newIndex: number): void;
getVariantsCount(): number;
getVariantPosition(variant: gdEventsBasedObjectVariant): number;
delete(): void;
ptr: number;
};

View File

@@ -40,6 +40,7 @@ declare class gdEventsFunctionsExtension {
getEventsBasedBehaviors(): gdEventsBasedBehaviorsList;
getEventsBasedObjects(): gdEventsBasedObjectsList;
serializeTo(element: gdSerializerElement): void;
serializeToExternal(element: gdSerializerElement): void;
unserializeFrom(project: gdProject, element: gdSerializerElement): void;
static isExtensionLifecycleEventsFunction(eventsFunctionName: string): boolean;
delete(): void;

View File

@@ -2,6 +2,7 @@
declare class gdObjectVariableHelper {
static mergeVariableContainers(objectsContainersList: gdObjectsContainersList, objectGroup: gdObjectGroup): gdVariablesContainer;
static fillAnyVariableBetweenObjects(globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, objectGroup: gdObjectGroup): void;
static applyChangesToVariants(eventsBasedObject: gdEventsBasedObject, objectName: string, changeset: gdVariablesChangeset): void;
delete(): void;
ptr: number;
};

View File

@@ -74,6 +74,7 @@ declare class libGDevelop {
VariablesContainersList: Class<gdVariablesContainersList>;
ObjectGroup: Class<gdObjectGroup>;
ObjectVariableHelper: Class<gdObjectVariableHelper>;
EventsBasedObjectVariantHelper: Class<gdEventsBasedObjectVariantHelper>;
ObjectGroupsContainer: Class<gdObjectGroupsContainer>;
PlatformSpecificAssets: Class<gdPlatformSpecificAssets>;
LoadingScreen: Class<gdLoadingScreen>;
@@ -222,6 +223,8 @@ declare class libGDevelop {
EventsBasedBehavior: Class<gdEventsBasedBehavior>;
EventsBasedBehaviorsList: Class<gdEventsBasedBehaviorsList>;
EventsBasedObject: Class<gdEventsBasedObject>;
EventsBasedObjectVariant: Class<gdEventsBasedObjectVariant>;
EventsBasedObjectVariantsContainer: Class<gdEventsBasedObjectVariantsContainer>;
EventsBasedObjectsList: Class<gdEventsBasedObjectsList>;
PropertiesContainer: Class<gdPropertiesContainer>;
EventsFunctionsExtension: Class<gdEventsFunctionsExtension>;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 KiB

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,8 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon-256.png">
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png">
<title>GDevelop 5</title>
<meta name="title" content="GDevelop game making app" />

View File

@@ -3,24 +3,14 @@
"name": "GDevelop",
"icons": [
{
"src": "favicon-512.png",
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "favicon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "favicon.ico",
"sizes": "128x128",
"type": "image/png"
}
],
"screenshots": [

View File

@@ -18,6 +18,7 @@ import {
installRequiredExtensions,
installPublicAsset,
type RequiredExtensionInstallation,
complyVariantsToEventsBasedObjectOf,
} from './InstallAsset';
import EventsFunctionsExtensionsContext from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsContext';
import { showErrorBox } from '../UI/Messages/MessageBox';
@@ -226,6 +227,7 @@ const AssetPackInstallDialog = ({
const createdObjects = results
.map(result => result.createdObjects)
.flat();
complyVariantsToEventsBasedObjectOf(project, createdObjects);
onAssetsAdded(createdObjects);
} catch (error) {
setAreAssetsBeingInstalled(false);

View File

@@ -24,7 +24,7 @@ import ErrorBoundary from '../../UI/ErrorBoundary';
type Props = {|
project: gdProject,
onClose: () => void,
onInstallExtension: ExtensionShortHeader => void,
onInstallExtension: (extensionName: string) => void,
onExtensionInstalled: (extensionName: string) => void,
onCreateNew?: () => void,
|};
@@ -63,7 +63,7 @@ const ExtensionsSearchDialog = ({
try {
let installedOrImportedExtensionName: string | null = null;
if (!!extensionShortHeader) {
onInstallExtension(extensionShortHeader);
onInstallExtension(extensionShortHeader.name);
const wasExtensionInstalledOrImported = await installDisplayedExtension(
i18n,
project,
@@ -77,7 +77,8 @@ const ExtensionsSearchDialog = ({
installedOrImportedExtensionName = await importExtension(
i18n,
eventsFunctionsExtensionsState,
project
project,
onInstallExtension
);
}

View File

@@ -47,7 +47,8 @@ export const installExtension = async (
export const importExtension = async (
i18n: I18nType,
eventsFunctionsExtensionsState: EventsFunctionsExtensionsState,
project: gdProject
project: gdProject,
onWillInstallExtension: (extensionName: string) => void
): Promise<string | null> => {
const eventsFunctionsExtensionOpener = eventsFunctionsExtensionsState.getEventsFunctionsExtensionOpener();
if (!eventsFunctionsExtensionOpener) return null;
@@ -69,6 +70,8 @@ export const importExtension = async (
if (!answer) return null;
}
onWillInstallExtension(serializedExtension.name);
await addSerializedExtensionsToProject(
eventsFunctionsExtensionsState,
project,

View File

@@ -155,6 +155,23 @@ export type InstallAssetArgs = {|
targetObjectFolderOrObject?: ?gdObjectFolderOrObject,
|};
const findVariant = (
container: gdEventsBasedObjectVariantsContainer,
assetStoreAssetId: string,
assetStoreOriginalName: string
): gdEventsBasedObjectVariant | null => {
for (let index = 0; index < container.getVariantsCount(); index++) {
const variant = container.getVariantAt(index);
if (
variant.getAssetStoreAssetId() === assetStoreAssetId &&
variant.getAssetStoreOriginalName() === assetStoreOriginalName
) {
return variant;
}
}
return null;
};
export const addAssetToProject = async ({
asset,
project,
@@ -170,6 +187,88 @@ export const addAssetToProject = async ({
const type: ?string = objectAsset.object.type;
if (!type) throw new Error('An object has no type specified');
const variantRenamings: Array<{
objectType: string,
oldVariantName: string,
newVariantName: string,
}> = [];
const serializedVariants = objectAsset.variants;
if (serializedVariants) {
// Install variants
for (const {
objectType,
variant: serializedVariant,
} of serializedVariants) {
if (project.hasEventsBasedObject(objectType)) {
const eventsBasedObject = project.getEventsBasedObject(objectType);
const variants = eventsBasedObject.getVariants();
let variant = findVariant(variants, asset.id, serializedVariant.name);
if (!variant) {
// TODO Forbid name with `::`
const uniqueNewName = newNameGenerator(
serializedVariant.name || asset.name,
tentativeNewName => variants.hasVariantNamed(tentativeNewName)
);
variant = variants.insertNewVariant(
uniqueNewName,
variants.getVariantsCount()
);
const variantName = variant.getName();
unserializeFromJSObject(
variant,
serializedVariant,
'unserializeFrom',
project
);
variant.setName(variantName);
variant.setAssetStoreAssetId(asset.id);
variant.setAssetStoreOriginalName(serializedVariant.name);
}
if (variant.getName() !== serializedVariant.name) {
variantRenamings.push({
objectType,
oldVariantName: serializedVariant.name,
newVariantName: variant.getName(),
});
}
}
}
// Update variant names into variants object configurations.
for (const {
objectType,
variant: serializedVariant,
} of serializedVariants) {
if (project.hasEventsBasedObject(objectType)) {
const eventsBasedObject = project.getEventsBasedObject(objectType);
const variants = eventsBasedObject.getVariants();
let variant = findVariant(variants, asset.id, serializedVariant.name);
if (variant) {
for (
let index = 0;
index < variant.getObjects().getObjectsCount();
index++
) {
const object = variant.getObjects().getObjectAt(index);
if (project.hasEventsBasedObject(object.getType())) {
const customObjectConfiguration = gd.asCustomObjectConfiguration(
object.getConfiguration()
);
const customObjectVariantRenaming = variantRenamings.find(
renaming => renaming.objectType === object.getType()
);
if (customObjectVariantRenaming) {
customObjectConfiguration.setVariantName(
customObjectVariantRenaming.newVariantName
);
}
}
}
}
}
}
}
// Insert the object
const originalName = sanitizeObjectName(objectAsset.object.name);
const newName = newNameGenerator(originalName, name =>
@@ -204,10 +303,27 @@ export const addAssetToProject = async ({
'unserializeFrom',
project
);
object.setAssetStoreId(asset.id);
// The name was overwritten after unserialization.
object.setName(newName);
object.setAssetStoreId(asset.id);
if (project.hasEventsBasedObject(object.getType())) {
const customObjectConfiguration = gd.asCustomObjectConfiguration(
object.getConfiguration()
);
if (customObjectConfiguration.getVariantName()) {
customObjectConfiguration.setMarkedAsOverridingEventsBasedObjectChildrenConfiguration(
false
);
}
const customObjectVariantRenaming = variantRenamings.find(
renaming => renaming.objectType === object.getType()
);
if (customObjectVariantRenaming) {
customObjectConfiguration.setVariantName(
customObjectVariantRenaming.newVariantName
);
}
}
// Add resources used by the object
objectAsset.resources.forEach(serializedResource => {
@@ -484,3 +600,21 @@ export const checkRequiredExtensionsUpdateForAssets = async ({
return checkRequiredExtensionsUpdate({ requiredExtensions, project });
};
export const complyVariantsToEventsBasedObjectOf = (
project: gdProject,
createdObjects: Array<gdObject>
) => {
const installedVariantObjectTypes = new Set<string>();
for (const createdObject of createdObjects) {
if (project.hasEventsBasedObject(createdObject.getType())) {
installedVariantObjectTypes.add(createdObject.getType());
}
}
for (const installedVariantObjectType of installedVariantObjectTypes) {
gd.EventsBasedObjectVariantHelper.complyVariantsToEventsBasedObject(
project,
project.getEventsBasedObject(installedVariantObjectType)
);
}
};

View File

@@ -20,6 +20,7 @@ import {
checkRequiredExtensionsUpdate,
checkRequiredExtensionsUpdateForAssets,
type InstallAssetOutput,
complyVariantsToEventsBasedObjectOf,
} from './InstallAsset';
import {
type Asset,
@@ -217,6 +218,10 @@ export const useInstallAsset = ({
openedAssetPack && openedAssetPack.id ? openedAssetPack.id : null,
assetPackKind: isPrivate ? 'private' : 'public',
});
complyVariantsToEventsBasedObjectOf(
project,
installOutput.createdObjects
);
await resourceManagementProps.onFetchNewlyAddedResources();
return installOutput;

View File

@@ -75,6 +75,10 @@ const Physics3DEditor = (props: Props) => {
const isStatic = properties.get('bodyType').getValue() === 'Static';
const canShapeBeOriented =
properties.get('shape').getValue() !== 'Sphere' &&
properties.get('shape').getValue() !== 'Box';
return (
<Column
expand
@@ -160,14 +164,15 @@ const Physics3DEditor = (props: Props) => {
id="physics3d-parameter-shape-orientation"
fullWidth
floatingLabelText={properties.get('shapeOrientation').getLabel()}
value={properties.get('shapeOrientation').getValue()}
value={
canShapeBeOriented
? properties.get('shapeOrientation').getValue()
: 'Z'
}
onChange={(e, i, newValue: string) =>
updateBehaviorProperty('shapeOrientation', newValue)
}
disabled={
properties.get('shape').getValue() === 'Sphere' ||
properties.get('shape').getValue() === 'Box'
}
disabled={!canShapeBeOriented}
>
<SelectOption key={'shape-orientation-z'} value={'Z'} label={t`Z`} />
<SelectOption key={'shape-orientation-y'} value={'Y'} label={t`Y`} />

View File

@@ -45,6 +45,7 @@ import CopyIcon from '../UI/CustomSvgIcons/Copy';
import ResponsiveFlatButton from '../UI/ResponsiveFlatButton';
import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
import QuickCustomizationPropertiesVisibilityDialog from '../QuickCustomization/QuickCustomizationPropertiesVisibilityDialog';
import Text from '../UI/Text';
const gd: libGDevelop = global.gd;
@@ -80,6 +81,7 @@ type BehaviorConfigurationEditorProps = {|
openBehaviorPropertiesQuickCustomizationDialog: (
behaviorName: string
) => void,
isListLocked: boolean,
|};
const BehaviorConfigurationEditor = React.forwardRef<
@@ -100,6 +102,7 @@ const BehaviorConfigurationEditor = React.forwardRef<
pasteBehaviors,
openExtension,
openBehaviorPropertiesQuickCustomizationDialog,
isListLocked,
},
ref
) => {
@@ -195,6 +198,7 @@ const BehaviorConfigurationEditor = React.forwardRef<
{
label: i18n._(t`Delete`),
click: () => onRemoveBehavior(behaviorName),
enabled: !isListLocked,
},
{
label: i18n._(t`Copy`),
@@ -203,7 +207,8 @@ const BehaviorConfigurationEditor = React.forwardRef<
{
label: i18n._(t`Paste`),
click: pasteBehaviors,
enabled: canPasteBehaviors,
// TODO Allow to paste behaviors that are already in the list.
enabled: canPasteBehaviors && !isListLocked,
},
...(project.hasEventsBasedBehavior(behaviorTypeName)
? [
@@ -618,6 +623,7 @@ type Props = {|
behaviorName: string
) => Promise<void>,
onExtensionInstalled: (extensionName: string) => void,
isListLocked: boolean,
|};
const BehaviorsEditor = (props: Props) => {
@@ -636,6 +642,7 @@ const BehaviorsEditor = (props: Props) => {
onUpdateBehaviorsSharedData,
openBehaviorEvents,
onExtensionInstalled,
isListLocked,
} = props;
const forceUpdate = useForceUpdate();
@@ -725,30 +732,41 @@ const BehaviorsEditor = (props: Props) => {
return (
<Column noMargin expand useFullHeight noOverflowParent>
{allVisibleBehaviors.length === 0 ? (
<Column noMargin expand justifyContent="center">
<EmptyPlaceholder
title={<Trans>Add your first behavior</Trans>}
description={
<Trans>
Behaviors add features to objects in a matter of clicks.
</Trans>
}
helpPagePath="/behaviors"
tutorialId="intro-behaviors-and-functions"
actionButtonId="add-behavior-button"
actionLabel={
isMobile ? <Trans>Add</Trans> : <Trans>Add a behavior</Trans>
}
onAction={openNewBehaviorDialog}
secondaryActionIcon={<PasteIcon />}
secondaryActionLabel={
isClipboardContainingBehaviors ? <Trans>Paste</Trans> : null
}
onSecondaryAction={() => {
pasteBehaviors();
}}
/>
</Column>
isListLocked ? (
<Column noMargin expand justifyContent="center">
<Text size="block-title" align="center">
<Trans>No behavior</Trans>
</Text>
<Text align="center" noMargin>
<Trans>There is no behavior to set up for this object.</Trans>
</Text>
</Column>
) : (
<Column noMargin expand justifyContent="center">
<EmptyPlaceholder
title={<Trans>Add your first behavior</Trans>}
description={
<Trans>
Behaviors add features to objects in a matter of clicks.
</Trans>
}
helpPagePath="/behaviors"
tutorialId="intro-behaviors-and-functions"
actionButtonId="add-behavior-button"
actionLabel={
isMobile ? <Trans>Add</Trans> : <Trans>Add a behavior</Trans>
}
onAction={openNewBehaviorDialog}
secondaryActionIcon={<PasteIcon />}
secondaryActionLabel={
isClipboardContainingBehaviors ? <Trans>Paste</Trans> : null
}
onSecondaryAction={() => {
pasteBehaviors();
}}
/>
</Column>
)
) : (
<React.Fragment>
<ScrollView ref={scrollView}>
@@ -778,6 +796,7 @@ const BehaviorsEditor = (props: Props) => {
canPasteBehaviors={isClipboardContainingBehaviors}
pasteBehaviors={pasteBehaviors}
resourceManagementProps={props.resourceManagementProps}
isListLocked={isListLocked}
/>
);
})}
@@ -806,7 +825,7 @@ const BehaviorsEditor = (props: Props) => {
onClick={() => {
pasteBehaviors();
}}
disabled={!isClipboardContainingBehaviors}
disabled={!isClipboardContainingBehaviors || isListLocked}
/>
</LineStackLayout>
<LineStackLayout justifyContent="flex-end" expand>
@@ -823,6 +842,7 @@ const BehaviorsEditor = (props: Props) => {
onClick={openNewBehaviorDialog}
icon={<Add />}
id="add-behavior-button"
disabled={isListLocked}
/>
</LineStackLayout>
</LineStackLayout>

View File

@@ -21,7 +21,9 @@ type Props = {|
onEventsFunctionsAdded: () => void,
onOpenCustomObjectEditor: () => void,
unsavedChanges?: ?UnsavedChanges,
onEventsBasedObjectChildrenEdited: () => void,
onEventsBasedObjectChildrenEdited: (
eventsBasedObject: gdEventsBasedObject
) => void,
|};
export default function EventsBasedObjectEditorPanel({

View File

@@ -23,7 +23,9 @@ type Props = {|
eventsBasedObject: gdEventsBasedObject,
onOpenCustomObjectEditor: () => void,
unsavedChanges?: ?UnsavedChanges,
onEventsBasedObjectChildrenEdited: () => void,
onEventsBasedObjectChildrenEdited: (
eventsBasedObject: gdEventsBasedObject
) => void,
|};
export default function EventsBasedObjectEditor({
@@ -127,7 +129,7 @@ export default function EventsBasedObjectEditor({
onCheck={(e, checked) => {
eventsBasedObject.markAsInnerAreaFollowingParentSize(checked);
onChange();
onEventsBasedObjectChildrenEdited();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
/>
{isDev && (
@@ -137,7 +139,7 @@ export default function EventsBasedObjectEditor({
onCheck={(e, checked) => {
eventsBasedObject.makAsUsingLegacyInstancesRenderer(checked);
onChange();
onEventsBasedObjectChildrenEdited();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
/>
)}
@@ -147,7 +149,7 @@ export default function EventsBasedObjectEditor({
onCheck={(e, checked) => {
eventsBasedObject.setPrivate(checked);
onChange();
onEventsBasedObjectChildrenEdited();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
}}
tooltipOrHelperText={
eventsBasedObject.isPrivate() ? (

View File

@@ -77,7 +77,9 @@ type Props = {|
unsavedChanges?: ?UnsavedChanges,
onOpenCustomObjectEditor: gdEventsBasedObject => void,
hotReloadPreviewButtonProps: HotReloadPreviewButtonProps,
onEventsBasedObjectChildrenEdited: () => void,
onEventsBasedObjectChildrenEdited: (
eventsBasedObject: gdEventsBasedObject
) => void,
onRenamedEventsBasedObject: (
eventsFunctionsExtension: gdEventsFunctionsExtension,
oldName: string,
@@ -778,7 +780,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
// Some custom object instances may target the pasted event-based object name.
// It can happen when an event-based object is deleted and another one is
// pasted to replace it.
this.props.onEventsBasedObjectChildrenEdited();
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject);
};
_onEventsBasedBehaviorRenamed = () => {
@@ -797,7 +799,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
}
};
_onEventsBasedObjectRenamed = () => {
_onEventsBasedObjectRenamed = (eventsBasedObject: gdEventsBasedObject) => {
// Name of an object changed, so notify parent
// that an object was edited (to trigger reload of extensions)
if (this.props.onObjectEdited) {
@@ -814,7 +816,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
// Some custom object instances may target the new event-based object name.
// It can happen when an event-based object is deleted and another one is
// renamed to replace it.
this.props.onEventsBasedObjectChildrenEdited();
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject);
};
_onDeleteEventsBasedBehavior = (
@@ -853,7 +855,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
eventsFunctionsExtension,
eventsBasedObject.getName()
);
onEventsBasedObjectChildrenEdited();
onEventsBasedObjectChildrenEdited(eventsBasedObject);
};
_onCloseExtensionFunctionSelectorDialog = (
@@ -1702,6 +1704,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
onCancel={() => this._editVariables(null)}
onApply={() => this._editVariables(null)}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}
isListLocked={false}
/>
)}
{objectMethodSelectorDialogOpen && selectedEventsBasedObject && (

View File

@@ -32,7 +32,10 @@ export default class BrowserEventsFunctionsExtensionWriter {
extension: gdEventsFunctionsExtension,
filename: string
): Promise<void> => {
const serializedObject = serializeToJSObject(extension);
const serializedObject = serializeToJSObject(
extension,
'serializeToExternal'
);
try {
await downloadStringContentAsFile(
filename,

View File

@@ -56,7 +56,10 @@ export default class LocalEventsFunctionsExtensionWriter {
extension: gdEventsFunctionsExtension,
filepath: string
): Promise<void> => {
const serializedObject = serializeToJSObject(extension);
const serializedObject = serializeToJSObject(
extension,
'serializeToExternal'
);
return writeJSONFile(serializedObject, filepath).catch(err => {
console.error('Unable to write the events function extension:', err);
throw err;

View File

@@ -133,6 +133,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
initiallySelectedVariableName={editorOpen.variableName}
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
</React.Fragment>

View File

@@ -131,6 +131,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
initiallySelectedVariableName={editorOpen.variableName}
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
</React.Fragment>

View File

@@ -127,6 +127,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
initiallySelectedVariableName={editorOpen.variableName}
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
</React.Fragment>

View File

@@ -84,6 +84,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
initiallySelectedVariableName={editorOpen.variableName}
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
</React.Fragment>

View File

@@ -227,6 +227,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
onComputeAllVariableNames={onComputeAllVariableNames}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
{editorOpen &&
@@ -246,6 +247,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
initiallySelectedVariableName={editorOpen.variableName}
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
onComputeAllVariableNames={onComputeAllVariableNames}
isListLocked={false}
/>
)}
</React.Fragment>

View File

@@ -110,6 +110,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
editorOpen.shouldCreate || false
}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
{editorOpen && eventsFunctionsExtension && !layout && (
@@ -122,6 +123,7 @@ export default React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
initiallySelectedVariableName={editorOpen.variableName}
shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate}
hotReloadPreviewButtonProps={null}
isListLocked={false}
/>
)}
</React.Fragment>

View File

@@ -2158,6 +2158,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
shouldCreateInitiallySelectedVariable={
this.state.editedVariable.shouldCreateVariable
}
isListLocked={false}
/>
)}
{this.state.layoutVariablesDialogOpen && (
@@ -2167,6 +2168,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
onCancel={() => this.editLayoutVariables(false)}
onApply={() => this.editLayoutVariables(false)}
hotReloadPreviewButtonProps={hotReloadPreviewButtonProps}
isListLocked={false}
/>
)}
{this.state.textEditedEvent && (

View File

@@ -57,16 +57,18 @@ const CustomTooltip = ({
payload,
label,
customStyle,
labelSuffix,
}: {|
payload: ?Array<any>,
label: string,
customStyle: Object,
labelSuffix: ?string,
|}) =>
payload ? (
<Paper style={customStyle} background="light">
<ColumnStackLayout>
<Text size="sub-title" noMargin>
{label}
{label} {labelSuffix ? labelSuffix : ''}
</Text>
{payload.length > 0 &&
payload.map(
@@ -167,7 +169,7 @@ export const BounceRateChart = ({
<LineChart data={chartData.overTime} margin={chartMargins}>
<RechartsLine
name={i18n._(t`Bounce rate`)}
unit="%"
unit={'%'}
formatter={minutesFormatter}
type="monotone"
dataKey="bounceRatePercent"
@@ -185,6 +187,7 @@ export const BounceRateChart = ({
style={styles.tickLabel}
/>
<YAxis
unit={'%'}
dataKey="bounceRatePercent"
stroke={gdevelopTheme.chart.textColor}
style={styles.tickLabel}
@@ -234,6 +237,7 @@ export const MeanPlayTimeChart = ({
style={styles.tickLabel}
/>
<YAxis
unit={` ` + i18n._(t`min`)}
dataKey="meanPlayedDurationInMinutes"
stroke={gdevelopTheme.chart.textColor}
style={styles.tickLabel}
@@ -272,6 +276,7 @@ export const PlayersRepartitionPerDurationChart = ({
yAxisId={0}
/>
<XAxis
unit={` ` + i18n._(t`min`)}
name={i18n._(t`Played time`)}
dataKey="duration"
type="number"
@@ -295,6 +300,7 @@ export const PlayersRepartitionPerDurationChart = ({
CustomTooltip({
...props,
customStyle: styles.tooltipContent,
labelSuffix: i18n._(t`minutes`),
})
}
/>
@@ -320,7 +326,7 @@ export const PlayersDurationPerDayChart = ({
type="monotone"
dataKey="over60sPlayersPercent"
formatter={percentFormatter}
unit={' %'}
unit={'%'}
stroke={gdevelopTheme.chart.dataColor1}
fill={gdevelopTheme.chart.dataColor1}
fillOpacity={0.15}
@@ -331,7 +337,7 @@ export const PlayersDurationPerDayChart = ({
type="monotone"
dataKey="over180sPlayersPercent"
formatter={percentFormatter}
unit={' %'}
unit={'%'}
stroke={gdevelopTheme.chart.dataColor1}
fill={gdevelopTheme.chart.dataColor1}
fillOpacity={0.15}
@@ -342,7 +348,7 @@ export const PlayersDurationPerDayChart = ({
type="monotone"
dataKey="over300sPlayersPercent"
formatter={percentFormatter}
unit={' %'}
unit={'%'}
stroke={gdevelopTheme.chart.dataColor1}
fill={gdevelopTheme.chart.dataColor1}
fillOpacity={0.15}
@@ -353,7 +359,7 @@ export const PlayersDurationPerDayChart = ({
type="monotone"
dataKey="over600sPlayersPercent"
formatter={percentFormatter}
unit={' %'}
unit={'%'}
stroke={gdevelopTheme.chart.dataColor1}
fill={gdevelopTheme.chart.dataColor1}
fillOpacity={0.15}
@@ -364,7 +370,7 @@ export const PlayersDurationPerDayChart = ({
type="monotone"
dataKey="over900sPlayersPercent"
formatter={percentFormatter}
unit={' %'}
unit={'%'}
stroke={gdevelopTheme.chart.dataColor1}
fill={gdevelopTheme.chart.dataColor1}
fillOpacity={0.15}
@@ -383,7 +389,7 @@ export const PlayersDurationPerDayChart = ({
dataKey="over60sPlayersPercent"
stroke={gdevelopTheme.chart.textColor}
style={styles.tickLabel}
unit={' %'}
unit={'%'}
/>
<Tooltip
content={props =>

View File

@@ -3,6 +3,7 @@ import * as React from 'react';
import { Trans, t } from '@lingui/macro';
import { I18n } from '@lingui/react';
import Grid from '@material-ui/core/Grid';
import Tooltip from '@material-ui/core/Tooltip';
import { formatISO, subDays } from 'date-fns';
import { Column, Line } from '../UI/Grid';
import {
@@ -21,6 +22,7 @@ import PlaceholderError from '../UI/PlaceholderError';
import SelectField from '../UI/SelectField';
import SelectOption from '../UI/SelectOption';
import PlaceholderLoader from '../UI/PlaceholderLoader';
import InfoIcon from '../UI/CustomSvgIcons/CircledInfo';
import { buildChartData, daysShownForYear } from './GameAnalyticsEvaluator';
import {
BounceRateChart,
@@ -142,6 +144,20 @@ export const GameAnalyticsPanel = ({
<Column noMargin alignItems="center" expand>
<Text size="block-title" align="center">
<Trans>{chartData.overview.playersCount} sessions</Trans>
<Tooltip
style={{ verticalAlign: 'bottom' }}
title={
<Text>
<Trans>
Number of people who launched the game. Viewers are
considered players when they stayed at least 60
seconds including loading screens.
</Trans>
</Text>
}
>
<InfoIcon />
</Tooltip>
</Text>
<SessionsChart
chartData={chartData}
@@ -169,6 +185,19 @@ export const GameAnalyticsPanel = ({
{Math.round(chartData.overview.bounceRatePercent)}% bounce
rate
</Trans>
<Tooltip
style={{ verticalAlign: 'bottom' }}
title={
<Text>
<Trans>
Percentage of people who leave before 60 seconds
including loading screens.
</Trans>
</Text>
}
>
<InfoIcon />
</Tooltip>
</Text>
<BounceRateChart
chartData={chartData}
@@ -186,6 +215,18 @@ export const GameAnalyticsPanel = ({
)}{' '}
minutes per player
</Trans>
<Tooltip
style={{ verticalAlign: 'bottom' }}
title={
<Text>
<Trans>
Is the average time a player spends in the game.
</Trans>
</Text>
}
>
<InfoIcon />
</Tooltip>
</Text>
<MeanPlayTimeChart
chartData={chartData}
@@ -209,6 +250,21 @@ export const GameAnalyticsPanel = ({
}{' '}
minutes
</Trans>
<Tooltip
style={{ verticalAlign: 'bottom' }}
title={
<Text>
<Trans>
Average of players still active after 15 minutes.
This graph shows how long players stay in the game
after X minutes. It helps to see if the players quit
quickly or keep playing for a while.
</Trans>
</Text>
}
>
<InfoIcon />
</Tooltip>
</Text>
<PlayersRepartitionPerDurationChart
chartData={chartData}
@@ -232,6 +288,23 @@ export const GameAnalyticsPanel = ({
}{' '}
minutes
</Trans>
<Tooltip
style={{ verticalAlign: 'bottom' }}
title={
<Text>
<Trans>
Shows how long players stay in the game over time.
The percentages are showing people playing for more
than 3, 5, 10, and 15 minutes based on the best day.
A higher value means better player retention on the
day. This helps you understand when players are most
engaged and when they drop off quickly.
</Trans>
</Text>
}
>
<InfoIcon />
</Tooltip>
</Text>
<PlayersDurationPerDayChart
chartData={chartData}

View File

@@ -119,7 +119,7 @@ const AnalyticsWidget = ({ game, onSeeAll, gameMetrics, gameUrl }: Props) => {
) : (
<Column expand noMargin>
<Line alignItems="center" justifyContent="space-between">
<Text size="block-title" noMargin>
<Text size="sub-title" noMargin>
<Trans>Sessions</Trans>
</Text>
<RaisedButton

View File

@@ -64,6 +64,7 @@ type Props = {|
historyHandler?: HistoryHandler,
tileMapTileSelection: ?TileMapTileSelection,
onSelectTileMapTile: (?TileMapTileSelection) => void,
isVariableListLocked: boolean,
|};
export const CompactInstancePropertiesEditor = ({
@@ -83,6 +84,7 @@ export const CompactInstancePropertiesEditor = ({
projectScopedContainersAccessor,
tileMapTileSelection,
onSelectTileMapTile,
isVariableListLocked,
}: Props) => {
const forceUpdate = useForceUpdate();
const variablesListRef = React.useRef<?VariablesListInterface>(null);
@@ -276,16 +278,18 @@ export const CompactInstancePropertiesEditor = ({
>
<ShareExternal style={styles.icon} />
</IconButton>
<IconButton
size="small"
onClick={
variablesListRef.current
? variablesListRef.current.addVariable
: undefined
}
>
<Add style={styles.icon} />
</IconButton>
{isVariableListLocked ? null : (
<IconButton
size="small"
onClick={
variablesListRef.current
? variablesListRef.current.addVariable
: undefined
}
>
<Add style={styles.icon} />
</IconButton>
)}
</Line>
</Line>
</Column>
@@ -314,6 +318,7 @@ export const CompactInstancePropertiesEditor = ({
compactEmptyPlaceholderText={
<Trans>There are no variables on this instance.</Trans>
}
isListLocked={isVariableListLocked}
/>
</>
) : null}

View File

@@ -7,14 +7,14 @@ import Rectangle from '../Utils/Rectangle';
type Props = {|
project: gdProject,
layout: gdLayout | null,
eventsBasedObject: gdEventsBasedObject | null,
eventsBasedObjectVariant: gdEventsBasedObjectVariant | null,
toCanvasCoordinates: (x: number, y: number) => [number, number],
|};
export default class WindowBorder {
project: gdProject;
layout: gdLayout | null;
eventsBasedObject: gdEventsBasedObject | null;
eventsBasedObjectVariant: gdEventsBasedObjectVariant | null;
toCanvasCoordinates: (x: number, y: number) => [number, number];
pixiRectangle = new PIXI.Graphics();
windowRectangle: Rectangle = new Rectangle();
@@ -22,12 +22,12 @@ export default class WindowBorder {
constructor({
project,
layout,
eventsBasedObject,
eventsBasedObjectVariant,
toCanvasCoordinates,
}: Props) {
this.project = project;
this.layout = layout;
this.eventsBasedObject = eventsBasedObject;
this.eventsBasedObjectVariant = eventsBasedObjectVariant;
this.toCanvasCoordinates = toCanvasCoordinates;
this.pixiRectangle.hitArea = new PIXI.Rectangle(0, 0, 0, 0);
@@ -38,15 +38,15 @@ export default class WindowBorder {
}
render() {
const { layout, eventsBasedObject } = this;
const { layout, eventsBasedObjectVariant } = this;
this.windowRectangle.set(
eventsBasedObject
eventsBasedObjectVariant
? {
left: eventsBasedObject.getAreaMinX(),
top: eventsBasedObject.getAreaMinY(),
right: eventsBasedObject.getAreaMaxX(),
bottom: eventsBasedObject.getAreaMaxY(),
left: eventsBasedObjectVariant.getAreaMinX(),
top: eventsBasedObjectVariant.getAreaMinY(),
right: eventsBasedObjectVariant.getAreaMaxX(),
bottom: eventsBasedObjectVariant.getAreaMaxY(),
}
: {
left: 0,
@@ -88,7 +88,7 @@ export default class WindowBorder {
displayedRectangle.width(),
displayedRectangle.height()
);
if (eventsBasedObject) {
if (eventsBasedObjectVariant) {
const origin = this.toCanvasCoordinates(0, 0);
this.pixiRectangle.drawRect(origin[0] - 8, origin[1] - 1, 16, 2);
this.pixiRectangle.drawRect(origin[0] - 1, origin[1] - 8, 2, 16);

View File

@@ -91,6 +91,7 @@ export type InstancesEditorPropsWithoutSizeAndScroll = {|
project: gdProject,
layout: gdLayout | null,
eventsBasedObject: gdEventsBasedObject | null,
eventsBasedObjectVariant: gdEventsBasedObjectVariant | null,
layersContainer: gdLayersContainer,
globalObjectsContainer: gdObjectsContainer | null,
objectsContainer: gdObjectsContainer,
@@ -564,7 +565,7 @@ export default class InstancesEditor extends Component<Props, State> {
this.windowBorder = new WindowBorder({
project: props.project,
layout: props.layout,
eventsBasedObject: props.eventsBasedObject,
eventsBasedObjectVariant: props.eventsBasedObjectVariant,
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
});
this.windowMask = new WindowMask({
@@ -1477,13 +1478,13 @@ export default class InstancesEditor extends Component<Props, State> {
};
_getAreaRectangle = (): Rectangle => {
const { eventsBasedObject, project } = this.props;
return eventsBasedObject
const { eventsBasedObjectVariant, project } = this.props;
return eventsBasedObjectVariant
? new Rectangle(
eventsBasedObject.getAreaMinX(),
eventsBasedObject.getAreaMinY(),
eventsBasedObject.getAreaMaxX(),
eventsBasedObject.getAreaMaxY()
eventsBasedObjectVariant.getAreaMinX(),
eventsBasedObjectVariant.getAreaMinY(),
eventsBasedObjectVariant.getAreaMaxX(),
eventsBasedObjectVariant.getAreaMaxY()
)
: new Rectangle(
0,

View File

@@ -188,18 +188,19 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
)
}
recommendedPlanIdIfNoSubscription="gdevelop_gold"
canHide
>
<Line>
<Column noMargin>
<Text noMargin>
{increaseQuotaOffering === 'subscribe' ? (
<Trans>
Get more free AI requests with a GDevelop premium plan.
Unlock AI requests included with a GDevelop premium plan.
</Trans>
) : (
<Trans>
Upgrade to another premium plan to get more free AI
requests.
Get even more AI requests included with a higher premium
plan.
</Trans>
)}
</Text>
@@ -232,45 +233,56 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
<Trans>What do you want to make?</Trans>
</Text>
</Column>
<Column noMargin alignItems="stretch" justifyContent="stretch">
<CompactTextAreaField
maxLength={6000}
value={userRequestText}
disabled={isLaunchingAiRequest}
onChange={userRequestText =>
setUserRequestText(userRequestText)
}
placeholder={newChatPlaceholder}
rows={5}
/>
</Column>
<Line noMargin>
<ResponsiveLineStackLayout
noMargin
alignItems="flex-start"
justifyContent="space-between"
expand
>
{!isMobile && errorOrQuotaOrCreditsExplanation}
<Line noMargin justifyContent="flex-end">
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
<RaisedButton
color="primary"
label={<Trans>Send</Trans>}
style={{ flexShrink: 0 }}
disabled={isLaunchingAiRequest}
onClick={() => {
onSendUserRequest(userRequestText);
}}
/>
</LeftLoader>
<form
onSubmit={() => {
onSendUserRequest(userRequestText);
}}
>
<ColumnStackLayout justifyContent="center" noMargin>
<Column noMargin alignItems="stretch" justifyContent="stretch">
<CompactTextAreaField
maxLength={6000}
value={userRequestText}
disabled={isLaunchingAiRequest}
onChange={userRequestText =>
setUserRequestText(userRequestText)
}
onSubmit={() => {
onSendUserRequest(userRequestText);
}}
placeholder={newChatPlaceholder}
rows={5}
/>
</Column>
<Line noMargin>
<ResponsiveLineStackLayout
noMargin
alignItems="flex-start"
justifyContent="space-between"
expand
>
{!isMobile && errorOrQuotaOrCreditsExplanation}
<Line noMargin justifyContent="flex-end">
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
<RaisedButton
color="primary"
label={<Trans>Send</Trans>}
style={{ flexShrink: 0 }}
disabled={isLaunchingAiRequest || !userRequestText}
onClick={() => {
onSendUserRequest(userRequestText);
}}
/>
</LeftLoader>
</Line>
{isMobile && errorOrQuotaOrCreditsExplanation}
</ResponsiveLineStackLayout>
</Line>
{isMobile && errorOrQuotaOrCreditsExplanation}
</ResponsiveLineStackLayout>
</Line>
</ColumnStackLayout>
</form>
{subscriptionBanner}
</ColumnStackLayout>
<Column justifyContent="center" noMargin>
<Column justifyContent="center">
<Text size="body-small" color="secondary" align="center" noMargin>
<Trans>
The AI is experimental and still being improved.{' '}
@@ -458,37 +470,52 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
) : (
subscriptionBanner
)}
<CompactTextAreaField
maxLength={6000}
value={userRequestText}
disabled={isLaunchingAiRequest}
onChange={userRequestText => setUserRequestText(userRequestText)}
placeholder={t`Ask a follow up question`}
rows={2}
/>
<Column noMargin alignItems="flex-end">
<ResponsiveLineStackLayout
<form
onSubmit={() => {
onSendUserRequest(userRequestText);
}}
>
<ColumnStackLayout
justifyContent="stretch"
alignItems="stretch"
noMargin
alignItems="flex-start"
justifyContent="space-between"
expand
>
{!isMobile && errorOrQuotaOrCreditsExplanation}
<Line noMargin justifyContent="flex-end">
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
<RaisedButton
color="primary"
disabled={aiRequest.status === 'working'}
label={<Trans>Send</Trans>}
onClick={() => {
onSendUserRequest(userRequestText);
}}
/>
</LeftLoader>
</Line>
{isMobile && errorOrQuotaOrCreditsExplanation}
</ResponsiveLineStackLayout>
</Column>
<CompactTextAreaField
maxLength={6000}
value={userRequestText}
disabled={isLaunchingAiRequest}
onChange={userRequestText => setUserRequestText(userRequestText)}
placeholder={t`Ask a follow up question`}
rows={2}
onSubmit={() => {
onSendUserRequest(userRequestText);
}}
/>
<Column noMargin alignItems="flex-end">
<ResponsiveLineStackLayout
noMargin
alignItems="flex-start"
justifyContent="space-between"
expand
>
{!isMobile && errorOrQuotaOrCreditsExplanation}
<Line noMargin justifyContent="flex-end">
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
<RaisedButton
color="primary"
disabled={aiRequest.status === 'working'}
label={<Trans>Send</Trans>}
onClick={() => {
onSendUserRequest(userRequestText);
}}
/>
</LeftLoader>
</Line>
{isMobile && errorOrQuotaOrCreditsExplanation}
</ResponsiveLineStackLayout>
</Column>
</ColumnStackLayout>
</form>
{dislikeFeedbackDialogOpenedFor && (
<DislikeFeedbackDialog
open

View File

@@ -63,7 +63,8 @@ export type RenderEditorContainerProps = {|
) => void,
onOpenCustomObjectEditor: (
gdEventsFunctionsExtension,
gdEventsBasedObject
gdEventsBasedObject,
variantName: string
) => void,
openObjectEvents: (extensionName: string, objectName: string) => void,
@@ -143,7 +144,9 @@ export type RenderEditorContainerProps = {|
// Object editing
openBehaviorEvents: (extensionName: string, behaviorName: string) => void,
onEventsBasedObjectChildrenEdited: () => void,
onEventsBasedObjectChildrenEdited: (
eventsBasedObject: gdEventsBasedObject
) => void,
onSceneObjectEdited: (
scene: gdLayout,
objectWithContext: ObjectWithContext
@@ -158,7 +161,17 @@ export type RenderEditorContainerProps = {|
extensionName: string,
eventsBasedObjectName: string
) => void,
onOpenEventBasedObjectVariantEditor: (
extensionName: string,
eventsBasedObjectName: string,
variantName: string
) => void,
onExtensionInstalled: (extensionName: string) => void,
onDeleteEventsBasedObjectVariant: (
eventsFunctionsExtension: gdEventsFunctionsExtension,
eventBasedObject: gdEventsBasedObject,
variant: gdEventsBasedObjectVariant
) => void,
|};
export type RenderEditorContainerPropsWithRef = {|

View File

@@ -120,9 +120,7 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
getEventsFunctionsExtension(): ?gdEventsFunctionsExtension {
const { project, projectItemName } = this.props;
if (!project || !projectItemName) return null;
const extensionName = gd.PlatformExtension.getExtensionFromFullObjectType(
projectItemName
);
const extensionName = projectItemName.split('::')[0] || '';
if (!project.hasEventsFunctionsExtensionNamed(extensionName)) {
return null;
@@ -144,9 +142,7 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
const extension = this.getEventsFunctionsExtension();
if (!extension) return null;
const eventsBasedObjectName = gd.PlatformExtension.getObjectNameFromFullObjectType(
projectItemName
);
const eventsBasedObjectName = projectItemName.split('::')[1] || '';
if (!extension.getEventsBasedObjects().has(eventsBasedObjectName)) {
return null;
@@ -154,6 +150,24 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
return extension.getEventsBasedObjects().get(eventsBasedObjectName);
}
getVariantName(): string {
const { projectItemName } = this.props;
return (projectItemName && projectItemName.split('::')[2]) || '';
}
getVariant(): ?gdEventsBasedObjectVariant {
const { project, projectItemName } = this.props;
if (!project || !projectItemName) return null;
const eventsBasedObject = this.getEventsBasedObject();
if (!eventsBasedObject) return null;
const variantName = projectItemName.split('::')[2] || '';
return eventsBasedObject.getVariants().hasVariantNamed(variantName)
? eventsBasedObject.getVariants().getVariant(variantName)
: eventsBasedObject.getDefaultVariant();
}
getEventsBasedObjectName(): ?string {
const { project, projectItemName } = this.props;
if (!project || !projectItemName) return null;
@@ -176,6 +190,9 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
const eventsBasedObject = this.getEventsBasedObject();
if (!eventsBasedObject) return null;
const variant = this.getVariant();
if (!variant) return null;
const projectScopedContainersAccessor = new ProjectScopedContainersAccessor(
{
project,
@@ -197,10 +214,11 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
layout={null}
eventsFunctionsExtension={eventsFunctionsExtension}
eventsBasedObject={eventsBasedObject}
eventsBasedObjectVariant={variant}
globalObjectsContainer={null}
objectsContainer={eventsBasedObject.getObjects()}
layersContainer={eventsBasedObject.getLayers()}
initialInstances={eventsBasedObject.getInitialInstances()}
objectsContainer={variant.getObjects()}
layersContainer={variant.getLayers()}
initialInstances={variant.getInitialInstances()}
getInitialInstancesEditorSettings={() =>
prepareInstancesEditorSettings(
{}, // TODO
@@ -216,13 +234,24 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
isActive={isActive}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}
openBehaviorEvents={this.props.openBehaviorEvents}
onObjectEdited={this.props.onEventsBasedObjectChildrenEdited}
onObjectEdited={() =>
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject)
}
onObjectGroupEdited={() =>
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject)
}
onEventsBasedObjectChildrenEdited={
this.props.onEventsBasedObjectChildrenEdited
}
onExtractAsEventBasedObject={this.props.onExtractAsEventBasedObject}
onOpenEventBasedObjectEditor={this.props.onOpenEventBasedObjectEditor}
onOpenEventBasedObjectVariantEditor={
this.props.onOpenEventBasedObjectVariantEditor
}
onExtensionInstalled={this.props.onExtensionInstalled}
onDeleteEventsBasedObjectVariant={
this.props.onDeleteEventsBasedObjectVariant
}
/>
</div>
);

View File

@@ -169,7 +169,8 @@ export class EventsFunctionsExtensionEditorContainer extends React.Component<Ren
onOpenCustomObjectEditor={eventsBasedObject => {
this.props.onOpenCustomObjectEditor(
eventsFunctionsExtension,
eventsBasedObject
eventsBasedObject,
''
);
}}
hotReloadPreviewButtonProps={this.props.hotReloadPreviewButtonProps}

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