diff --git a/Core/GDCore/Project/ObjectTools.cpp b/Core/GDCore/Project/ObjectTools.cpp new file mode 100644 index 000000000000..773f2b0c279e --- /dev/null +++ b/Core/GDCore/Project/ObjectTools.cpp @@ -0,0 +1,57 @@ +/* + * GDevelop Core + * Copyright 2008-2025 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "ObjectTools.h" + +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/ObjectMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Platform.h" + +namespace gd { + +bool ObjectTools::IsBehaviorCompatibleWithObject( + const gd::Platform &platform, const gd::String &objectType, + const gd::String &behaviorType, + std::unordered_set coveredBehaviorType) { + bool isBehaviorTypeAlreadyCovered = + !coveredBehaviorType.insert(behaviorType).second; + if (isBehaviorTypeAlreadyCovered) { + return true; + } + const gd::BehaviorMetadata &behaviorMetadata = + MetadataProvider::GetBehaviorMetadata(platform, behaviorType); + if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) { + // Should not happen because the behavior was added successfully (so its + // metadata are valid) - but double check anyway and bail out if the + // behavior metadata are invalid. + return false; + } + if (!behaviorMetadata.GetObjectType().empty() && + behaviorMetadata.GetObjectType() != objectType) { + return false; + } + for (const gd::String &requiredBehaviorType : + behaviorMetadata.GetRequiredBehaviorTypes()) { + const gd::BehaviorMetadata &requiredBehaviorMetadata = + gd::MetadataProvider::GetBehaviorMetadata(platform, requiredBehaviorType); + if (requiredBehaviorMetadata.IsHidden()) { + const gd::ObjectMetadata &objectMetadata = + gd::MetadataProvider::GetObjectMetadata(platform, objectType); + if (objectMetadata.GetDefaultBehaviors().find(requiredBehaviorType) == + objectMetadata.GetDefaultBehaviors().end()) { + // A capability is missing in the object. + return false; + } + } + if (!gd::ObjectTools::IsBehaviorCompatibleWithObject( + platform, objectType, requiredBehaviorType, coveredBehaviorType)) { + return false; + } + } + return true; +} + +} // namespace gd diff --git a/Core/GDCore/Project/ObjectTools.h b/Core/GDCore/Project/ObjectTools.h new file mode 100644 index 000000000000..2a2a817e7a24 --- /dev/null +++ b/Core/GDCore/Project/ObjectTools.h @@ -0,0 +1,35 @@ +/* + * 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/String.h" +#include + +namespace gd { +class Platform; +class Object; +} // namespace gd + +namespace gd { + +class GD_CORE_API ObjectTools { +public: + static bool IsBehaviorCompatibleWithObject(const gd::Platform &platform, + const gd::String &objectType, + const gd::String &behaviorType) { + std::unordered_set coveredBehaviorType; + return IsBehaviorCompatibleWithObject(platform, objectType, behaviorType, + coveredBehaviorType); + } + +private: + static bool IsBehaviorCompatibleWithObject( + const gd::Platform &platform, const gd::String &objectType, + const gd::String &behaviorType, + std::unordered_set coveredBehaviorType); +}; + +} // namespace gd diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index c604508a0d23..aadd075c412c 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2786,6 +2786,13 @@ interface WholeProjectRefactorer { [Value] SetString STATIC_FindAllLeaderboardIds([Ref] Project project); }; +interface ObjectTools { + boolean STATIC_IsBehaviorCompatibleWithObject( + [Const, Ref] Platform platform, + [Const] DOMString objectType, + [Const] DOMString behaviorType); +}; + interface EventsBasedObjectDependencyFinder { boolean STATIC_IsDependentFromEventsBasedObject( [Const, Ref] Project project, diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index e79c8ac9f7a3..14d1634e9e6d 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -78,6 +78,7 @@ #include #include #include +#include #include #include #include @@ -659,6 +660,7 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_FindInvalidRequiredBehaviorProperties \ FindInvalidRequiredBehaviorProperties #define STATIC_GetBehaviorsWithType GetBehaviorsWithType +#define STATIC_IsBehaviorCompatibleWithObject IsBehaviorCompatibleWithObject #define STATIC_FixInvalidRequiredBehaviorProperties \ FixInvalidRequiredBehaviorProperties #define STATIC_RemoveLayerInScene RemoveLayerInScene diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 2f851f6a9684..fad8247caf91 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1985,6 +1985,10 @@ export class WholeProjectRefactorer extends EmscriptenObject { static findAllLeaderboardIds(project: Project): SetString; } +export class ObjectTools extends EmscriptenObject { + static isBehaviorCompatibleWithObject(platform: Platform, objectType: string, behaviorType: string): boolean; +} + export class EventsBasedObjectDependencyFinder extends EmscriptenObject { static isDependentFromEventsBasedObject(project: Project, eventsBasedObject: EventsBasedObject, dependency: EventsBasedObject): boolean; } diff --git a/GDevelop.js/types/gdobjecttools.js b/GDevelop.js/types/gdobjecttools.js new file mode 100644 index 000000000000..f9006152204a --- /dev/null +++ b/GDevelop.js/types/gdobjecttools.js @@ -0,0 +1,6 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdObjectTools { + static isBehaviorCompatibleWithObject(platform: gdPlatform, objectType: string, behaviorType: string): boolean; + delete(): void; + ptr: number; +}; \ No newline at end of file diff --git a/GDevelop.js/types/libgdevelop.js b/GDevelop.js/types/libgdevelop.js index b6d12fcf4df2..629cd0b35069 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -184,6 +184,7 @@ declare class libGDevelop { ResourceExposer: Class; VariablesChangeset: Class; WholeProjectRefactorer: Class; + ObjectTools: Class; EventsBasedObjectDependencyFinder: Class; PropertyFunctionGenerator: Class; UsedExtensionsResult: Class; diff --git a/newIDE/app/src/BehaviorsEditor/index.js b/newIDE/app/src/BehaviorsEditor/index.js index 27c3d9689859..bff4a42ac8fc 100644 --- a/newIDE/app/src/BehaviorsEditor/index.js +++ b/newIDE/app/src/BehaviorsEditor/index.js @@ -477,6 +477,15 @@ export const useManageObjectBehaviors = ({ if (!name || !type || !serializedBehavior) { return; } + if ( + !gd.ObjectTools.isBehaviorCompatibleWithObject( + project.getCurrentPlatform(), + object.getType(), + type + ) + ) { + return; + } const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata( project.getCurrentPlatform(),