Fill empty behavior parameters in actions/conditions when a behavior is attached to an object (#6633)

This commit is contained in:
D8H
2024-06-07 17:09:58 +02:00
committed by GitHub
parent 1b13127a2f
commit ab63e02862
10 changed files with 214 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
/*
* GDevelop Core
* Copyright 2008-2024 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "BehaviorParametersFiller.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Instruction.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
bool BehaviorParametersFiller::DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) {
const auto &metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
if (parameterMetadata.GetValueTypeMetadata().IsBehavior() &&
parameterValue.GetPlainString().length() == 0) {
auto &expectedBehaviorTypeName =
parameterMetadata.GetValueTypeMetadata().GetExtraInfo();
auto &objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto behaviorNames =
objectsContainersList.GetBehaviorsOfObject(lastObjectName, true);
gd::String foundBehaviorName = "";
for (auto &behaviorName : behaviorNames) {
auto behaviorTypeName =
objectsContainersList.GetTypeOfBehavior(behaviorName, false);
if (behaviorTypeName == expectedBehaviorTypeName) {
foundBehaviorName = behaviorName;
break;
}
}
if (!foundBehaviorName.empty()) {
instruction.SetParameter(parameterIndex,
gd::Expression(foundBehaviorName));
}
}
});
return false;
}
BehaviorParametersFiller::~BehaviorParametersFiller() {}
} // namespace gd

View File

@@ -0,0 +1,45 @@
/*
* GDevelop Core
* Copyright 2008-2024 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
#include <map>
#include <memory>
#include <vector>
namespace gd {
class Instruction;
class Platform;
class ProjectScopedContainers;
} // namespace gd
namespace gd {
/**
* \brief Fill empty behavior parameters with any behavior that matches the
* required behavior type.
*
* \ingroup IDE
*/
class GD_CORE_API BehaviorParametersFiller : public ArbitraryEventsWorker {
public:
BehaviorParametersFiller(
const gd::Platform &platform_,
const gd::ProjectScopedContainers &projectScopedContainers_)
: platform(platform_),
projectScopedContainers(projectScopedContainers_){};
virtual ~BehaviorParametersFiller();
private:
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
const gd::Platform &platform;
const gd::ProjectScopedContainers &projectScopedContainers;
};
} // namespace gd

View File

@@ -26,6 +26,7 @@
#include "GDCore/IDE/Events/InstructionsTypeRenamer.h"
#include "GDCore/IDE/Events/LinkEventTargetRenamer.h"
#include "GDCore/IDE/Events/ProjectElementRenamer.h"
#include "GDCore/IDE/Events/BehaviorParametersFiller.h"
#include "GDCore/IDE/EventsFunctionTools.h"
#include "GDCore/IDE/Project/ArbitraryBehaviorSharedDataWorker.h"
#include "GDCore/IDE/Project/ArbitraryEventBasedBehaviorsWorker.h"
@@ -1537,6 +1538,16 @@ void WholeProjectRefactorer::ObjectRemovedInLayout(
}
}
void WholeProjectRefactorer::BehaviorsAddedToObjectInLayout(
gd::Project &project, gd::Layout &layout, const gd::String &objectName) {
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
gd::BehaviorParametersFiller behaviorParameterFiller(
project.GetCurrentPlatform(), projectScopedContainers);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
project, layout, behaviorParameterFiller);
}
void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
gd::Project &project, gd::Layout &layout, const gd::String &oldName,
const gd::String &newName, bool isObjectGroup) {
@@ -1801,6 +1812,17 @@ void WholeProjectRefactorer::GlobalObjectRemoved(
}
}
void WholeProjectRefactorer::BehaviorsAddedToGlobalObject(
gd::Project &project, const gd::String &objectName) {
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
gd::Layout &layout = project.GetLayout(i);
if (layout.HasObjectNamed(objectName))
continue;
BehaviorsAddedToObjectInLayout(project, layout, objectName);
}
}
void WholeProjectRefactorer::RemoveLayer(gd::Project &project,
gd::Layout &layout,
const gd::String &layerName) {

View File

@@ -395,6 +395,18 @@ class GD_CORE_API WholeProjectRefactorer {
gd::Layout& layout,
const gd::String& objectName);
/**
* \brief Refactor the project after behaviors are added to an object in a
* layout.
*
* This will update the layout, all external events associated with it.
* The refactor is actually applied to all objects because it allow to handle
* groups.
*/
static void BehaviorsAddedToObjectInLayout(gd::Project &project,
gd::Layout &layout,
const gd::String &objectName);
/**
* \brief Refactor the project after an object is removed in an events-based
* object.
@@ -467,6 +479,16 @@ class GD_CORE_API WholeProjectRefactorer {
static void GlobalObjectRemoved(gd::Project& project,
const gd::String& objectName);
/**
* \brief Refactor the project after behaviors are added a global object.
*
* This will update all the layouts, all external events associated with them.
* The refactor is actually applied to all objects because it allow to handle
* groups.
*/
void BehaviorsAddedToGlobalObject(gd::Project &project,
const gd::String &objectName);
/**
* \brief Return the set of all the types of the objects that are using the
* given behavior.

View File

@@ -1475,6 +1475,41 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
}
}
SECTION("Behaviors added to an object (in layout)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &scene = project.InsertNewLayout("Scene", 0);
auto &object =
scene.InsertNewObject(project, "MyExtension::Sprite", "Object", 0);
gd::StandardEvent &event =
dynamic_cast<gd::StandardEvent &>(scene.GetEvents().InsertNewEvent(
project, "BuiltinCommonInstructions::Standard"));
// Add a behavior instruction using an object that doesn't have the
// behavior.
{
gd::Instruction action;
action.SetType("MyExtension::BehaviorDoSomething");
action.SetParametersCount(3);
action.SetParameter(0, gd::Expression("Object"));
// The behavior parameter is left empty.
action.SetParameter(1, gd::Expression(""));
action.SetParameter(2, gd::Expression("0"));
event.GetActions().Insert(action);
}
// Attach the behavior to the object.
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
gd::WholeProjectRefactorer::BehaviorsAddedToObjectInLayout(project, scene,
"Object");
// The behavior parameter is now filled.
REQUIRE(event.GetActions()[0].GetParameter(1).GetPlainString() ==
"MyBehavior");
}
SECTION("Object renamed (in events function)") {
SECTION("Group") {
gd::Project project;

View File

@@ -2438,6 +2438,10 @@ interface WholeProjectRefactorer {
[Ref] Project project,
[Ref] Layout layout,
[Const] DOMString objectName);
void STATIC_BehaviorsAddedToObjectInLayout(
[Ref] Project project,
[Ref] Layout layout,
[Const] DOMString objectName);
void STATIC_ObjectOrGroupRenamedInEventsFunction([Ref] Project project, [Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer globalObjectsContainer, [Ref] ObjectsContainer objectsContainer, [Const] DOMString oldName, [Const] DOMString newName, boolean isObjectGroup);
void STATIC_ObjectRemovedInEventsFunction(
[Ref] Project project,
@@ -2456,6 +2460,9 @@ interface WholeProjectRefactorer {
void STATIC_GlobalObjectRemoved(
[Ref] Project project,
[Const] DOMString objectName);
void STATIC_BehaviorsAddedToGlobalObject(
[Ref] Project project,
[Const] DOMString objectName);
[Value] SetString STATIC_GetAllObjectTypesUsingEventsBasedBehavior([Const, Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior);
void STATIC_EnsureBehaviorEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior);
void STATIC_EnsureObjectEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedObject eventsBasedObject);

View File

@@ -644,6 +644,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
#define STATIC_Date Date
#define STATIC_ObjectOrGroupRenamedInLayout ObjectOrGroupRenamedInLayout
#define STATIC_ObjectRemovedInLayout ObjectRemovedInLayout
#define STATIC_BehaviorsAddedToObjectInLayout BehaviorsAddedToObjectInLayout
#define STATIC_ObjectRemovedInEventsFunction \
ObjectRemovedInEventsFunction
#define STATIC_ObjectOrGroupRenamedInEventsFunction \
@@ -654,6 +655,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
ObjectOrGroupRenamedInEventsBasedObject
#define STATIC_GlobalObjectOrGroupRenamed GlobalObjectOrGroupRenamed
#define STATIC_GlobalObjectRemoved GlobalObjectRemoved
#define STATIC_BehaviorsAddedToGlobalObject BehaviorsAddedToGlobalObject
#define STATIC_GetAllObjectTypesUsingEventsBasedBehavior \
GetAllObjectTypesUsingEventsBasedBehavior
#define STATIC_EnsureBehaviorEventsFunctionsProperParameters \

View File

@@ -1829,12 +1829,14 @@ export class WholeProjectRefactorer extends EmscriptenObject {
static renameObjectEffect(project: Project, layout: Layout, gdObject: gdObject, oldName: string, newName: string): void;
static objectOrGroupRenamedInLayout(project: Project, layout: Layout, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectRemovedInLayout(project: Project, layout: Layout, objectName: string): void;
static behaviorsAddedToObjectInLayout(project: Project, layout: Layout, objectName: string): void;
static objectOrGroupRenamedInEventsFunction(project: Project, eventsFunction: EventsFunction, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectRemovedInEventsFunction(project: Project, eventsFunction: EventsFunction, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, objectName: string): void;
static objectOrGroupRenamedInEventsBasedObject(project: Project, globalObjectsContainer: ObjectsContainer, eventsBasedObject: EventsBasedObject, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectRemovedInEventsBasedObject(project: Project, eventsBasedObject: EventsBasedObject, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, objectName: string): void;
static globalObjectOrGroupRenamed(project: Project, oldName: string, newName: string, isObjectGroup: boolean): void;
static globalObjectRemoved(project: Project, objectName: string): void;
static behaviorsAddedToGlobalObject(project: Project, objectName: string): void;
static getAllObjectTypesUsingEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior): SetString;
static ensureBehaviorEventsFunctionsProperParameters(eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior): void;
static ensureObjectEventsFunctionsProperParameters(eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject): void;

View File

@@ -25,12 +25,14 @@ declare class gdWholeProjectRefactorer {
static renameObjectEffect(project: gdProject, layout: gdLayout, gdObject: gdObject, oldName: string, newName: string): void;
static objectOrGroupRenamedInLayout(project: gdProject, layout: gdLayout, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectRemovedInLayout(project: gdProject, layout: gdLayout, objectName: string): void;
static behaviorsAddedToObjectInLayout(project: gdProject, layout: gdLayout, objectName: string): void;
static objectOrGroupRenamedInEventsFunction(project: gdProject, eventsFunction: gdEventsFunction, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectRemovedInEventsFunction(project: gdProject, eventsFunction: gdEventsFunction, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, objectName: string): void;
static objectOrGroupRenamedInEventsBasedObject(project: gdProject, globalObjectsContainer: gdObjectsContainer, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectRemovedInEventsBasedObject(project: gdProject, eventsBasedObject: gdEventsBasedObject, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, objectName: string): void;
static globalObjectOrGroupRenamed(project: gdProject, oldName: string, newName: string, isObjectGroup: boolean): void;
static globalObjectRemoved(project: gdProject, objectName: string): void;
static behaviorsAddedToGlobalObject(project: gdProject, objectName: string): void;
static getAllObjectTypesUsingEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): gdSetString;
static ensureBehaviorEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): void;
static ensureObjectEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject): void;

View File

@@ -1832,6 +1832,18 @@ export default class SceneEditor extends React.Component<Props, State> {
this.reloadResourcesFor(
editedObjectWithContext.object
);
if (editedObjectWithContext.global) {
gd.WholeProjectRefactorer.behaviorsAddedToGlobalObject(
project,
editedObjectWithContext.object.getName()
);
} else {
gd.WholeProjectRefactorer.behaviorsAddedToObjectInLayout(
project,
layout,
editedObjectWithContext.object.getName()
);
}
}
this.editObject(null);
this.updateBehaviorsSharedData();