// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "SmartObjectTypes.h" #include "SmartObjectDefinition.h" #include "SmartObjectRuntime.generated.h" #define UE_API SMARTOBJECTSMODULE_API class USmartObjectComponent; namespace UE::SmartObject { uint16 GetMaskForEnabledReasonTag(const FGameplayTag Tag); } /** * Enumeration to represent the runtime state of a slot */ UENUM() enum class ESmartObjectSlotState : uint8 { Invalid, /** Slot is available */ Free, /** Slot is claimed but interaction is not active yet */ Claimed, /** Slot is claimed and interaction is active */ Occupied, /** Slot can no longer be claimed or used since the parent object and its slot are disabled (e.g. instance tags) */ Disabled UE_DEPRECATED(5.2, "Use IsEnabled() instead."), }; /** * Indicates if the subsystem should try to spawn the actor associated to the smartobject * if it is currently owned by an instanced actor. */ UENUM() enum class ETrySpawnActorIfDehydrated : uint8 { No, Yes }; /** * Struct describing a reservation between a user and a smart object slot. */ USTRUCT(BlueprintType) struct FSmartObjectClaimHandle { GENERATED_BODY() FSmartObjectClaimHandle(const FSmartObjectHandle InSmartObjectHandle, const FSmartObjectSlotHandle InSlotHandle, const FSmartObjectUserHandle& InUser) : SmartObjectHandle(InSmartObjectHandle), SlotHandle(InSlotHandle), UserHandle(InUser) {} FSmartObjectClaimHandle() {} bool operator==(const FSmartObjectClaimHandle& Other) const { return SmartObjectHandle == Other.SmartObjectHandle && SlotHandle == Other.SlotHandle && UserHandle == Other.UserHandle; } bool operator!=(const FSmartObjectClaimHandle& Other) const { return !(*this == Other); } friend FString LexToString(const FSmartObjectClaimHandle& Handle) { return FString::Printf(TEXT("Object:%s Slot:%s User:%s"), *LexToString(Handle.SmartObjectHandle), *LexToString(Handle.SlotHandle), *LexToString(Handle.UserHandle)); } void Invalidate() { *this = InvalidHandle; } /** * Indicates that the handle was properly assigned by a call to 'Claim' but doesn't guarantee that the associated * object and slot are still registered in the simulation. * This information requires a call to `USmartObjectSubsystem::IsClaimedObjectValid` using the handle. */ bool IsValid() const { return SmartObjectHandle.IsValid() && SlotHandle.IsValid() && UserHandle.IsValid(); } static UE_API const FSmartObjectClaimHandle InvalidHandle; /** Handle to the Smart Object where the claimed slot belongs to. */ UPROPERTY(BlueprintReadOnly, EditAnywhere, Transient, Category="Default") FSmartObjectHandle SmartObjectHandle; /** Handle of the claimed slot. */ UPROPERTY(BlueprintReadOnly, EditAnywhere, Transient, Category="Default") FSmartObjectSlotHandle SlotHandle; /** Handle describing the user which claimed the slot. */ UPROPERTY(EditAnywhere, Transient, Category="Default") FSmartObjectUserHandle UserHandle; }; /** * Runtime data holding the final slot transform (i.e. parent transform applied on slot local offset and rotation) */ USTRUCT() struct UE_DEPRECATED(5.3, "Transform is moved to FSmartObjectRuntimeSlot.") SMARTOBJECTSMODULE_API FSmartObjectSlotTransform : public FSmartObjectSlotStateData { GENERATED_BODY() const FTransform& GetTransform() const { return Transform; } FTransform& GetMutableTransform() { return Transform; } void SetTransform(const FTransform& InTransform) { Transform = InTransform; } protected: UPROPERTY(Transient) FTransform Transform; }; /** Delegate to notify when a given slot gets invalidated and the interaction must be aborted */ DECLARE_DELEGATE_TwoParams(FOnSlotInvalidated, const FSmartObjectClaimHandle&, ESmartObjectSlotState /* Current State */); /** * Struct to store and manage state of a runtime instance associated to a given slot definition */ USTRUCT() struct FSmartObjectRuntimeSlot { GENERATED_BODY() public: /* Provide default constructor to be able to compile template instantiation 'UScriptStruct::TCppStructOps' */ /* Also public to pass void 'UScriptStruct::TCppStructOps::ConstructForTests(void *)' */ FSmartObjectRuntimeSlot() : bSlotEnabled(true), bObjectEnabled(true) {} FVector3f GetSlotOffset() const { return Offset; } FRotator3f GetSlotRotation() const { return Rotation; } FTransform GetSlotLocalTransform() const { return FTransform(FRotator(Rotation), FVector(Offset)); } FTransform GetSlotWorldTransform(const FTransform& OwnerTransform) const { return FTransform(FRotator(Rotation), FVector(Offset)) * OwnerTransform; } /** @return Current claim state of the slot. */ ESmartObjectSlotState GetState() const { return State; } /** * Sets the slot claimed. * @param ClaimPriority Claim priority, a slot claimed at lower priority can be claimed by higher priority (unless already in use). * @return True if the slot can be claimed. */ bool CanBeClaimed(ESmartObjectClaimPriority ClaimPriority) const { return IsEnabled() && (State == ESmartObjectSlotState::Free || (State == ESmartObjectSlotState::Claimed && ClaimedPriority < ClaimPriority)); } /** @return the runtime gameplay tags of the slot. */ const FGameplayTagContainer& GetTags() const { return Tags; } /** @return true if both the slot and its parent smart object are enabled. */ bool IsEnabled() const { return bSlotEnabled && bObjectEnabled; } /** @return User data struct that can be associated to the slot when claimed or used. */ FConstStructView GetUserData() const { return UserData; } FInstancedStructContainer& GetMutableStateData() { return StateData; } const FInstancedStructContainer& GetStateData() const { return StateData; } /** Indicates if preconditions were successfully initialized. */ bool ArePreconditionsInitialized() const { return PreconditionState.IsInitialized(); } protected: /** Struct could have been nested inside the subsystem but not possible with USTRUCT */ friend class USmartObjectSubsystem; friend struct FSmartObjectRuntime; UE_API bool Claim(const FSmartObjectUserHandle& InUser, ESmartObjectClaimPriority ClaimPriority); UE_API bool Release(const FSmartObjectClaimHandle& ClaimHandle, const bool bAborted); friend FString LexToString(const FSmartObjectRuntimeSlot& Slot) { return FString::Printf(TEXT("User:%s State:%s"), *LexToString(Slot.User), *UEnum::GetValueAsString(Slot.State)); } /** Offset of the slot relative to the Smart Object. */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FVector3f Offset = FVector3f::ZeroVector; /** Rotation of the slot relative to the Smart Object. */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FRotator3f Rotation = FRotator3f::ZeroRotator; /** Runtime tags associated with this slot. */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FGameplayTagContainer Tags; /** Struct used to store contextual data of the user when claiming or using a slot. */ FInstancedStruct UserData; /** Slot state data that can be added at runtime. */ FInstancedStructContainer StateData; /** Delegate used to notify when a slot gets invalidated. See RegisterSlotInvalidationCallback */ FOnSlotInvalidated OnSlotInvalidatedDelegate; /** Handle to the user that reserves or uses the slot */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FSmartObjectUserHandle User; /** World condition runtime state. */ UPROPERTY(Transient) mutable FWorldConditionQueryState PreconditionState; /** Current availability state of the slot */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) ESmartObjectSlotState State = ESmartObjectSlotState::Free; UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) ESmartObjectClaimPriority ClaimedPriority = ESmartObjectClaimPriority::None; /** True if the slot is enabled */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) uint8 bSlotEnabled : 1; /** True if the parent smart object is enabled */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) uint8 bObjectEnabled : 1; }; /** * Struct to store and manage state of a runtime instance associated to a given smart object definition */ USTRUCT() struct FSmartObjectRuntime { GENERATED_BODY() public: /* Provide default constructor to be able to compile template instantiation 'UScriptStruct::TCppStructOps' */ /* Also public to pass void 'UScriptStruct::TCppStructOps::ConstructForTests(void *)' */ FSmartObjectRuntime() { } FSmartObjectRuntime(const FSmartObjectRuntime& Other) = default; FSmartObjectRuntime& operator=(const FSmartObjectRuntime& Other) = default; FSmartObjectRuntime(FSmartObjectRuntime&& Other) : PreconditionState(MoveTemp(Other.PreconditionState)) , Slots(MoveTemp(Other.Slots)) , Definition(MoveTemp(Other.Definition)) , OwnerComponent(MoveTemp(Other.OwnerComponent)) , OwnerData(MoveTemp(Other.OwnerData)) , Transform(MoveTemp(Other.Transform)) , Tags(MoveTemp(Other.Tags)) , OnEvent(MoveTemp(Other.OnEvent)) , RegisteredHandle(MoveTemp(Other.RegisteredHandle)) , SpatialEntryData(MoveTemp(Other.SpatialEntryData)) #if UE_ENABLE_DEBUG_DRAWING , Bounds(MoveTemp(Other.Bounds)) #endif // UE_ENABLE_DEBUG_DRAWING , DisableFlags(Other.DisableFlags) { } FSmartObjectRuntime& operator=(FSmartObjectRuntime&& Other) { if (this == &Other) { return *this; } PreconditionState = MoveTemp(Other.PreconditionState); Slots = MoveTemp(Other.Slots); Definition = MoveTemp(Other.Definition); OwnerComponent = MoveTemp(Other.OwnerComponent); OwnerData = MoveTemp(Other.OwnerData); Transform = MoveTemp(Other.Transform); Tags = MoveTemp(Other.Tags); OnEvent = MoveTemp(Other.OnEvent); RegisteredHandle = MoveTemp(Other.RegisteredHandle); SpatialEntryData = MoveTemp(Other.SpatialEntryData); #if UE_ENABLE_DEBUG_DRAWING Bounds = MoveTemp(Other.Bounds); #endif // UE_ENABLE_DEBUG_DRAWING DisableFlags = Other.DisableFlags; return *this; } FSmartObjectHandle GetRegisteredHandle() const { return RegisteredHandle; } const FTransform& GetTransform() const { return Transform; } const USmartObjectDefinition& GetDefinition() const { checkf(Definition != nullptr, TEXT("Initialized from a valid reference from the constructor")); return *Definition; } /** Returns all tags assigned to the smart object instance */ const FGameplayTagContainer& GetTags() const { return Tags; } /** @return reference to the Smart Object event delegate. */ const FOnSmartObjectEvent& GetEventDelegate() const { return OnEvent; } /** @return mutable reference to the Smart Object event delegate. */ FOnSmartObjectEvent& GetMutableEventDelegate() { return OnEvent; } /** * Indicates if the Smart Object is enabled regardless of the reason. * @return True of the Smart Object is enabled. */ bool IsEnabled() const { return DisableFlags == 0; } /** * Indicates if the Smart Object is enabled based on a specific reason. * @param ReasonTag Valid Tag to specify the reason for changing the enabled state of the object. Method will ensure if not valid (i.e. None). * @return True of the Smart Object is enabled. */ UE_API bool IsEnabledForReason(FGameplayTag ReasonTag) const; /** * Enables or disables the entire smart object. * @param ReasonTag Valid Tag to specify the reason for changing the enabled state of the object. Method will ensure if not valid (i.e. None). * @param bEnabled Flag indicating if the object should be enabled or not. */ UE_API void SetEnabled(FGameplayTag ReasonTag, bool bEnabled); /** * Returns the actor associated to the smart object instance. * @param TrySpawnActorIfDehydrated Indicates if the instance should try to spawn the actor/component * associated to the smartobject if it is currently owned by an instanced actor. * @return Pointer to owner actor if present. */ UE_API AActor* GetOwnerActor(ETrySpawnActorIfDehydrated TrySpawnActorIfDehydrated = ETrySpawnActorIfDehydrated::No) const; /** * Returns the actor associated to the smart object instance. * @param TrySpawnActorIfDehydrated Indicates if the instance should try to spawn the actor/component * associated to the smartobject if it is currently owned by an instanced actor. * @return Pointer to owning component if present. */ UE_API USmartObjectComponent* GetOwnerComponent(ETrySpawnActorIfDehydrated TrySpawnActorIfDehydrated = ETrySpawnActorIfDehydrated::No) const; /** @return handle of the specified slot. */ const FSmartObjectRuntimeSlot& GetSlot(const int32 Index) const { return Slots[Index]; } FSmartObjectRuntimeSlot& GetMutableSlot(const int32 Index) { return Slots[Index]; } TConstArrayView GetSlots() const { return Slots; } /** Indicates if preconditions were successfully initialized. */ bool ArePreconditionsInitialized() const { return PreconditionState.IsInitialized(); } #if WITH_SMARTOBJECT_DEBUG UE_API FString DebugGetDisableFlagsString() const; #endif // WITH_SMARTOBJECT_DEBUG private: /** Struct could have been nested inside the subsystem but not possible with USTRUCT */ friend class USmartObjectSubsystem; UE_API explicit FSmartObjectRuntime(const USmartObjectDefinition& Definition); void SetTransform(const FTransform& Value) { Transform = Value; } void SetRegisteredHandle(const FSmartObjectHandle Value) { RegisteredHandle = Value; } /** * Enables or disables the entire smart object using the bit mask from a reason tag. * @param bEnabled Flag indicating if the object should be enabled or not. * @param ReasonMask Bit mask associated to the reason for disabling the object. */ UE_API void SetEnabled(bool bEnabled, uint16 ReasonMask); /** * Creates full actor from instanced actor owner, if any. * That actor will register its SmartObjectComponents that will then update OwnerComponent. */ UE_API bool ResolveOwnerActor() const; /** World condition runtime state. */ UPROPERTY(Transient) mutable FWorldConditionQueryState PreconditionState; /** Runtime slots */ UPROPERTY(Transient) TArray Slots; /** Associated smart object definition */ UPROPERTY() TObjectPtr Definition = nullptr; /** Component that owns the Smart Object. May be empty if the parent Actor is not loaded. */ UPROPERTY() TWeakObjectPtr OwnerComponent; /** Struct used to store contextual data of the owner of that SmartObject. */ FInstancedStruct OwnerData; /** Instance specific transform */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FTransform Transform; /** Tags applied to the current instance */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FGameplayTagContainer Tags; /** Delegate that is fired when the Smart Object changes. */ FOnSmartObjectEvent OnEvent; /** RegisteredHandle != FSmartObjectHandle::Invalid when registered with SmartObjectSubsystem */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) FSmartObjectHandle RegisteredHandle; /** Spatial representation data associated to the current instance */ UPROPERTY(EditDefaultsOnly, Category = "SmartObject", meta = (BaseStruct = "/Script/SmartObjectsModule.SmartObjectSpatialEntryData", ExcludeBaseStruct)) FInstancedStruct SpatialEntryData; #if UE_ENABLE_DEBUG_DRAWING FBox Bounds = FBox(EForceInit::ForceInit); #endif /** * Each slot has its own enabled state but the parent instance also have a more high level state that could be split into different reasons. * Note: The enabled state is stored as disable bits to make it easier to check for "is the object disabled for a given or any reason". */ UPROPERTY(Transient, VisibleAnywhere, Category=SmartObjects) uint16 DisableFlags = 0; public: static constexpr int32 MaxNumDisableFlags = sizeof(DisableFlags) * 8; }; USTRUCT() struct FConstSmartObjectSlotView { GENERATED_BODY() public: FConstSmartObjectSlotView() = default; bool IsValid() const { return SlotHandle.IsValid() && Runtime && Slot; } FSmartObjectSlotHandle GetSlotHandle() const { return SlotHandle; } /** * Returns a reference to the slot state data of the specified type. * Method will fail a check if the slot doesn't have the given type. */ template const T& GetStateData() const { static_assert(TIsDerivedFrom::IsDerived, "Given struct doesn't represent a valid runtime data type. Make sure to inherit from FSmartObjectSlotStateData or one of its child-types."); checkf(Slot, TEXT("StateData can only be accessed through a valid SlotView")); const T* Item = nullptr; for (FConstStructView Data : Slot->GetStateData()) { Item = Data.GetPtr(); if (Item != nullptr) { break; } } check(Item); return *Item; } /** * Returns a pointer to the slot state data of the specified type. * Method will return null if the slot doesn't have the given type. */ template const T* GetStateDataPtr() const { static_assert(TIsDerivedFrom::IsDerived, "Given struct doesn't represent a valid runtime data type. Make sure to inherit from FSmartObjectSlotStateData or one of its child-types."); checkf(Slot, TEXT("StateData can only be accessed through a valid SlotView")); for (FConstStructView Data : Slot->GetStateData()) { if (const T* Item = Data.GetPtr()) { return Item; } } return nullptr; } /** * Returns a reference to the definition of the slot's parent object. * Method will fail a check if called on an invalid SlotView. * @note The definition fragment is always created and assigned when creating an entity associated to a slot * so a valid SlotView is guaranteed to be able to provide it. */ const USmartObjectDefinition& GetSmartObjectDefinition() const { checkf(Runtime, TEXT("Definition can only be accessed through a valid SlotView")); return Runtime->GetDefinition(); } /** * Returns a reference to the main definition of the slot. * Method will fail a check if called on an invalid SlotView. */ const FSmartObjectSlotDefinition& GetDefinition() const { checkf(Runtime, TEXT("Definition can only be accessed through a valid SlotView")); return Runtime->GetDefinition().GetSlot(SlotHandle.GetSlotIndex()); } /** * Fills the provided GameplayTagContainer with the activity tags associated to the slot according to the tag filtering policy. * Method will fail a check if called on an invalid SlotView. */ void GetActivityTags(FGameplayTagContainer& OutActivityTags) const { GetSmartObjectDefinition().GetSlotActivityTags(GetDefinition(), OutActivityTags); } /** * Returns a reference to the definition data of the specified type from the main slot definition. * Method will fail a check if the slot definition doesn't contain the given type. */ template const T& GetDefinitionData() const { const FSmartObjectSlotDefinition& SlotDefinition = GetDefinition(); return SlotDefinition.GetDefinitionData(); } /** * Returns a pointer to the definition data of the specified type from the main slot definition. * Method will return null if the slot doesn't contain the given type. */ template const T* GetDefinitionDataPtr() const { const FSmartObjectSlotDefinition& SlotDefinition = GetDefinition(); return SlotDefinition.GetDefinitionDataPtr(); } /** @return the claim state of the slot. */ ESmartObjectSlotState GetState() const { checkf(Slot, TEXT("State can only be accessed through a valid SlotView")); return Slot->GetState(); } /** @return true of the slot can be claimed. */ bool CanBeClaimed(ESmartObjectClaimPriority ClaimPriority) const { checkf(Slot, TEXT("Claim can only be accessed through a valid SlotView")); return Slot->CanBeClaimed(ClaimPriority); } /** @return true if the slot and the object is enabled. */ bool IsEnabled() const { checkf(Slot, TEXT("Enabled can only be accessed through a valid SlotView")); return Slot->IsEnabled(); } /** @return runtime gameplay tags of the slot. */ const FGameplayTagContainer& GetTags() const { checkf(Slot, TEXT("Tags can only be accessed through a valid SlotView")); return Slot->GetTags(); } /** @return handle to the owning Smart Object. */ FSmartObjectHandle GetOwnerRuntimeObject() const { return SlotHandle.GetSmartObjectHandle(); } protected: friend class USmartObjectSubsystem; FConstSmartObjectSlotView(const FSmartObjectSlotHandle InSlotHandle, const FSmartObjectRuntime& InRuntime, const FSmartObjectRuntimeSlot& InSlot) : SlotHandle(InSlotHandle) , Runtime(&InRuntime) , Slot(&InSlot) {} FSmartObjectSlotHandle SlotHandle; const FSmartObjectRuntime* Runtime; const FSmartObjectRuntimeSlot* Slot; }; USTRUCT() struct FSmartObjectSlotView : public FConstSmartObjectSlotView { GENERATED_BODY() using FConstSmartObjectSlotView::FConstSmartObjectSlotView; /** * Returns a reference to the mutable slot state data of the specified type. * Method will fail a check if the slot doesn't have the given type. */ template T& GetMutableStateData() const { static_assert(TIsDerivedFrom::IsDerived, "Given struct doesn't represent a valid runtime data type. Make sure to inherit from FSmartObjectSlotStateData or one of its child-types."); checkf(Slot, TEXT("StateData can only be accessed through a valid SlotView")); T* Item = nullptr; for (FStructView Data : const_cast(Slot)->GetMutableStateData()) { Item = Data.GetPtr(); if (Item != nullptr) { break; } } check(Item); return *Item; } /** * Returns a pointer to the mutable slot state data of the specified type. * Method will return null if the slot doesn't have the given type. */ template T* GetMutableStateDataPtr() const { static_assert(TIsDerivedFrom::IsDerived, "Given struct doesn't represent a valid runtime data type. Make sure to inherit from FSmartObjectSlotStateData or one of its child-types."); checkf(Slot, TEXT("StateData can only be accessed through a valid SlotView")); for (FStructView Data : const_cast(Slot)->GetMutableStateData()) { if (T* Item = Data.GetPtr()) { return Item; } } return nullptr; } }; #undef UE_API