From fcca6160683d4f793e02396f6d06499178ddb6a7 Mon Sep 17 00:00:00 2001 From: Rachel Blackman Date: Tue, 2 Jul 2024 17:57:38 -0700 Subject: [PATCH 01/63] Minor Cleanup (#86) * Ensure Unbound attributes are replicated properly at startup. * Additional guards on unbinding filtered tag delegates for gameplay element mapping. * Ensure Unbound attributes are replicated properly at startup. * Ensure the garbage check on gameplay element maps works in 5.3 and earlier. --- .../Animation/GMCAbilityAnimInstance.cpp | 5 +++ .../Components/GMCAbilityComponent.cpp | 27 ++++++++++--- .../Utility/GameplayElementMapping.cpp | 38 +++++++++++++++++-- .../Public/Animation/GMCAbilityAnimInstance.h | 1 + .../Public/Utility/GameplayElementMapping.h | 4 +- 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Animation/GMCAbilityAnimInstance.cpp b/Source/GMCAbilitySystem/Private/Animation/GMCAbilityAnimInstance.cpp index 304013fa..76f89887 100644 --- a/Source/GMCAbilitySystem/Private/Animation/GMCAbilityAnimInstance.cpp +++ b/Source/GMCAbilitySystem/Private/Animation/GMCAbilityAnimInstance.cpp @@ -11,6 +11,11 @@ UGMCAbilityAnimInstance::UGMCAbilityAnimInstance(const FObjectInitializer& Objec #endif } +UGMCAbilityAnimInstance::~UGMCAbilityAnimInstance() +{ + TagPropertyMap.Reset(); +} + void UGMCAbilityAnimInstance::NativeInitializeAnimation() { Super::NativeInitializeAnimation(); diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 60e61b83..94fb1aee 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -45,12 +45,22 @@ FDelegateHandle UGMC_AbilitySystemComponent::AddFilteredTagChangeDelegate(const void UGMC_AbilitySystemComponent::RemoveFilteredTagChangeDelegate(const FGameplayTagContainer& Tags, FDelegateHandle Handle) { + if (!Handle.IsValid()) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Passed an invalid delegate to unbind for tag changes on %s"), *Tags.ToString()) + return; + } + for (int32 Index = FilteredTagDelegates.Num() - 1; Index >= 0; --Index) { TPair& SearchPair = FilteredTagDelegates[Index]; if (SearchPair.Key == Tags) { - SearchPair.Value.Remove(Handle); + if (!SearchPair.Value.Remove(Handle)) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Unable to unbind a tag change delegate for %s"), *Tags.ToString()) + } + if (!SearchPair.Value.IsBound()) { FilteredTagDelegates.RemoveAt(Index); @@ -555,7 +565,7 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() if(AttributeData.bGMCBound){ BoundAttributes.AddAttribute(NewAttribute); } - else if (GetOwnerRole() == ROLE_Authority) { + else if (GetOwnerRole() == ROLE_Authority || GetNetMode() == NM_Standalone) { // FFastArraySerializer will duplicate all attributes on first replication if we // add the attributes on the clients as well. UnBoundAttributes.AddAttribute(NewAttribute); @@ -570,10 +580,13 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() Attribute.CalculateValue(); } - for (const FAttribute& Attribute : UnBoundAttributes.Items) + // We need to be non-const to ensure we can mark the item dirty. + for (FAttribute& Attribute : UnBoundAttributes.Items) { Attribute.CalculateValue(); + UnBoundAttributes.MarkItemDirty(Attribute); } + UnBoundAttributes.MarkArrayDirty(); } void UGMC_AbilitySystemComponent::SetStartingTags() @@ -1149,8 +1162,12 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi } AffectedAttribute->ApplyModifier(AttributeModifier, bModifyBaseValue); - OnAttributeChanged.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); - NativeAttributeChangeDelegate.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); + // Only broadcast a change if we've genuinely changed. + if (OldValue != AffectedAttribute->Value) + { + OnAttributeChanged.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); + NativeAttributeChangeDelegate.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); + } BoundAttributes.MarkAttributeDirty(*AffectedAttribute); UnBoundAttributes.MarkAttributeDirty(*AffectedAttribute); diff --git a/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp b/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp index 21a82bef..eb72749c 100644 --- a/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp +++ b/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp @@ -1,6 +1,7 @@ #include "Utility/GameplayElementMapping.h" #include "GMCAbilityComponent.h" #include "Misc/DataValidation.h" +#include "Misc/EngineVersionComparison.h" FGMCGameplayElementTagPropertyMap::FGMCGameplayElementTagPropertyMap() { @@ -14,7 +15,7 @@ FGMCGameplayElementTagPropertyMap::FGMCGameplayElementTagPropertyMap(const FGMCG FGMCGameplayElementTagPropertyMap::~FGMCGameplayElementTagPropertyMap() { - Unregister(); + Reset(); } #if WITH_EDITOR @@ -245,8 +246,14 @@ void FGMCGameplayElementTagPropertyMap::Unregister() CachedAbilityComponent = nullptr; } +void FGMCGameplayElementTagPropertyMap::Reset() +{ + Unregister(); + AttributeHandle.Reset(); +} + void FGMCGameplayElementTagPropertyMap::GameplayTagChangedCallback(const FGameplayTagContainer& AddedTags, - const FGameplayTagContainer& RemovedTags) + const FGameplayTagContainer& RemovedTags) { UObject* Owner = CachedOwner.Get(); const UGMC_AbilitySystemComponent* AbilityComponent = CachedAbilityComponent.Get(); @@ -269,14 +276,39 @@ void FGMCGameplayElementTagPropertyMap::GameplayTagChangedCallback(const FGamepl } } +#if UE_VERSION_OLDER_THAN(5, 4, 0) +#define GARBAGE_FLAG RF_Garbage +#else +#define GARBAGE_FLAG RF_MirroredGarbage +#endif + void FGMCGameplayElementTagPropertyMap::GameplayAttributeChangedCallback(const FGameplayTag& AttributeTag, const float OldValue, const float NewValue) { UObject* Owner = CachedOwner.Get(); const UGMC_AbilitySystemComponent* AbilityComponent = CachedAbilityComponent.Get(); - + if (!Owner || !AbilityComponent) { + // Get an object pointer even if it's prepped for garbage collection. + UObject *TrueOwner = CachedOwner.Get(true); + + // Disable deprecation warnings since we need to use RF_Garbage (deprecated) prior to 5.4 introducing + // RF_MirroredGarbage. +PRAGMA_DISABLE_DEPRECATION_WARNINGS + if (TrueOwner != nullptr && TrueOwner->HasAnyFlags(GARBAGE_FLAG)) + { + // This happens if our animation blueprint is being used as a child layer; it will be marked for garbage + // collection, but not deallocated (so the Reset() function hasn't yet been called). + // + // In this case, we want to just reset ourselves as though we were being deallocated and bail. + + Reset(); + return; + } +PRAGMA_ENABLE_DEPRECATION_WARNINGS + + // We don't have even a pending-delete object, meaning something has gone VERY wrong. UE_LOG(LogGMCAbilitySystem, Warning, TEXT("FGMCGameplayElementTagPropertyMap: Received callback on uninitialized map!")); return; } diff --git a/Source/GMCAbilitySystem/Public/Animation/GMCAbilityAnimInstance.h b/Source/GMCAbilitySystem/Public/Animation/GMCAbilityAnimInstance.h index 7c7696db..6a21e4f9 100644 --- a/Source/GMCAbilitySystem/Public/Animation/GMCAbilityAnimInstance.h +++ b/Source/GMCAbilitySystem/Public/Animation/GMCAbilityAnimInstance.h @@ -19,6 +19,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityAnimInstance : public UAnimInstance public: UGMCAbilityAnimInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + virtual ~UGMCAbilityAnimInstance(); virtual void NativeBeginPlay() override; virtual void NativeInitializeAnimation() override; diff --git a/Source/GMCAbilitySystem/Public/Utility/GameplayElementMapping.h b/Source/GMCAbilitySystem/Public/Utility/GameplayElementMapping.h index 181a38a9..e63df1be 100644 --- a/Source/GMCAbilitySystem/Public/Utility/GameplayElementMapping.h +++ b/Source/GMCAbilitySystem/Public/Utility/GameplayElementMapping.h @@ -60,6 +60,8 @@ struct GMCABILITYSYSTEM_API FGMCGameplayElementTagPropertyMap // Must be called to actually start using this instance. void Initialize(UObject* Owner, UGMC_AbilitySystemComponent* AbilitySystemComponent); + void Unregister(); + void Reset(); void ApplyCurrentTags(); void ApplyCurrentAttributes(); @@ -76,8 +78,6 @@ struct GMCABILITYSYSTEM_API FGMCGameplayElementTagPropertyMap bool SetValueForMappedProperty(FProperty* Property, float PropValue); bool SetValueForMappedProperty(FProperty* Property, FGameplayTagContainer& MatchedTags); - void Unregister(); - void GameplayTagChangedCallback(const FGameplayTagContainer& AddedTags, const FGameplayTagContainer& RemovedTags); void GameplayAttributeChangedCallback(const FGameplayTag& AttributeTag, const float OldValue, const float NewValue); From 1837b2fd106b09848dd168eecd6007ebc5fb4fad Mon Sep 17 00:00:00 2001 From: Bean's Beans Studios Date: Tue, 2 Jul 2024 17:58:33 -0700 Subject: [PATCH 02/63] Add methods to add/remove starting effects (#84) --- .../Private/Components/GMCAbilityComponent.cpp | 16 ++++++++++++++++ .../Public/Components/GMCAbilityComponent.h | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 94fb1aee..4bba1908 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -206,6 +206,22 @@ void UGMC_AbilitySystemComponent::RemoveAbilityMapData(UGMCAbilityMapData* Abili } } +void UGMC_AbilitySystemComponent::AddStartingEffects(TArray> EffectsToAdd) +{ + for (const TSubclassOf Effect : EffectsToAdd) + { + StartingEffects.AddUnique(Effect); + } +} + +void UGMC_AbilitySystemComponent::RemoveStartingEffects(TArray> EffectsToRemove) +{ + for (const TSubclassOf Effect : EffectsToRemove) + { + StartingEffects.Remove(Effect); + } +} + void UGMC_AbilitySystemComponent::GrantAbilityByTag(const FGameplayTag AbilityTag) { if (!GrantedAbilityTags.HasTagExact(AbilityTag)) diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index a149b057..ea708ee1 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -73,7 +73,13 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") void RemoveAbilityMapData(UGMCAbilityMapData* AbilityMapData); - + + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") + void AddStartingEffects(TArray> EffectsToAdd); + + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") + void RemoveStartingEffects(TArray> EffectsToRemove); + // Add an ability to the GrantedAbilities array UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") void GrantAbilityByTag(const FGameplayTag AbilityTag); From 452576556b7d87b03863ee512163d4514bb884a2 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 15 Jul 2024 18:04:03 -0700 Subject: [PATCH 03/63] Fix heartbeats for locally controlled server pawns causing timeouts --- .../Private/Ability/Tasks/GMCAbilityTaskBase.cpp | 6 +++++- .../GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp index 53410d29..421518ba 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp @@ -30,6 +30,10 @@ void UGMCAbilityTaskBase::RegisterTask(UGMCAbilityTaskBase* Task) void UGMCAbilityTaskBase::Tick(float DeltaTime) { + // Locally controlled server pawns don't need to send heartbeats + if (AbilitySystemComponent->GMCMovementComponent->IsLocallyControlledServerPawn()) return; + + // If not the server version of the component, send heartbeats if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) { if (ClientLastHeartbeatSentTime + HeartbeatInterval < AbilitySystemComponent->ActionTimer) @@ -38,7 +42,7 @@ void UGMCAbilityTaskBase::Tick(float DeltaTime) ClientLastHeartbeatSentTime = AbilitySystemComponent->ActionTimer; } } - else if (LastHeartbeatReceivedTime + HeartbeatMaxInterval < AbilitySystemComponent->ActionTimer) + if (LastHeartbeatReceivedTime + HeartbeatMaxInterval < AbilitySystemComponent->ActionTimer) { UE_LOG(LogGMCReplication, Error, TEXT("Server Task Heartbeat Timeout, Cancelling Ability: %s"), *Ability->GetName()); Ability->EndAbility(); diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp index f608c548..9843ca0e 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp @@ -36,6 +36,10 @@ void UGMCAbilityTask_WaitDelay::Tick(float DeltaTime) void UGMCAbilityTask_WaitDelay::OnTimeFinish() { - Completed.Broadcast(); - EndTask(); + if (!bTaskCompleted) + { + bTaskCompleted = true; + Completed.Broadcast(); + EndTask(); + } } From b6236ba202ea32702734aee5abc282d0c544c554 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 15 Jul 2024 19:06:54 -0700 Subject: [PATCH 04/63] Add WaitForGMCMontageChange --- .../Ability/Tasks/GMCAbilityTaskBase.cpp | 2 +- .../Private/Ability/Tasks/WaitDelay.cpp | 3 +- .../Ability/Tasks/WaitForGMCMontageChange.cpp | 62 +++++++++++++++++++ .../Ability/Tasks/WaitForGMCMontageChange.h | 34 ++++++++++ 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp index 421518ba..bb862823 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp @@ -42,7 +42,7 @@ void UGMCAbilityTaskBase::Tick(float DeltaTime) ClientLastHeartbeatSentTime = AbilitySystemComponent->ActionTimer; } } - if (LastHeartbeatReceivedTime + HeartbeatMaxInterval < AbilitySystemComponent->ActionTimer) + else if (LastHeartbeatReceivedTime + HeartbeatMaxInterval < AbilitySystemComponent->ActionTimer) { UE_LOG(LogGMCReplication, Error, TEXT("Server Task Heartbeat Timeout, Cancelling Ability: %s"), *Ability->GetName()); Ability->EndAbility(); diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp index 9843ca0e..c526be94 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitDelay.cpp @@ -36,9 +36,8 @@ void UGMCAbilityTask_WaitDelay::Tick(float DeltaTime) void UGMCAbilityTask_WaitDelay::OnTimeFinish() { - if (!bTaskCompleted) + if (GetState() != EGameplayTaskState::Finished) { - bTaskCompleted = true; Completed.Broadcast(); EndTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp new file mode 100644 index 00000000..17911953 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp @@ -0,0 +1,62 @@ +#include "Ability/Tasks/WaitForGMCMontageChange.h" + +#include "GMCOrganicMovementComponent.h" +#include "Components/GMCAbilityComponent.h" + + + +UGMCAbilityTask_WaitForGMCMontageChange::UGMCAbilityTask_WaitForGMCMontageChange(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UGMCAbilityTask_WaitForGMCMontageChange* UGMCAbilityTask_WaitForGMCMontageChange::WaitForGMCMontageChange(UGMCAbility* OwningAbility) +{ + UGMCAbilityTask_WaitForGMCMontageChange* Task = NewAbilityTask(OwningAbility); + return Task; +} + +void UGMCAbilityTask_WaitForGMCMontageChange::Activate() +{ + Super::Activate(); + bTickingTask = true; + + OrganicMovementCmp = Cast(AbilitySystemComponent->GMCMovementComponent); + + if (OrganicMovementCmp == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("WaitForGMCMontageChange called without an Organic Movement Component")); + OnFinish(); + return; + } + + StartingMontage = OrganicMovementCmp->GetActiveMontage(OrganicMovementCmp->MontageTracker); + if (StartingMontage == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("WaitForGMCMontageChange called without a running montage")); + OnFinish(); + } +} + +void UGMCAbilityTask_WaitForGMCMontageChange::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + const UAnimMontage* RunningMontage = OrganicMovementCmp->GetActiveMontage(OrganicMovementCmp->MontageTracker); + + // If the montage has changed, finish the task + if (StartingMontage != RunningMontage) + { + OnFinish(); + } +} + +void UGMCAbilityTask_WaitForGMCMontageChange::OnFinish() +{ + if (GetState() != EGameplayTaskState::Finished) + { + Completed.Broadcast(); + EndTask(); + } +} diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h new file mode 100644 index 00000000..b934c765 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h @@ -0,0 +1,34 @@ +#pragma once +#include "GMCAbilityTaskBase.h" +#include "GMCOrganicMovementComponent.h" +#include "WaitForGMCMontageChange.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGMCAbilityTaskWaitForGMCMontageChangeDelayOutputPin); + +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForGMCMontageChange : public UGMCAbilityTaskBase +{ + GENERATED_UCLASS_BODY() + + virtual void Activate() override; + virtual void Tick(float DeltaTime) override; + + UPROPERTY(BlueprintAssignable) + FGMCAbilityTaskWaitForGMCMontageChangeDelayOutputPin Completed; + + /** Return debug string describing task */ + // virtual FString GetDebugString() const override; + + /** Triggers if the montage changes (Allows for Networked Interrupt). *ONLY WORKS FOR ORGANIC MOVEMENT COMPONENT* */ + UFUNCTION(BlueprintCallable, Category="GMCAbility|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE")) + static UGMCAbilityTask_WaitForGMCMontageChange* WaitForGMCMontageChange(UGMCAbility* OwningAbility); + + UPROPERTY() + UAnimMontage* StartingMontage; + + UPROPERTY() + UGMC_OrganicMovementCmp* OrganicMovementCmp; + +private: + void OnFinish(); +}; \ No newline at end of file From b267baa057552de4c7c06c0643e312f226a4554a Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 15 Jul 2024 20:42:47 -0700 Subject: [PATCH 05/63] Effects:Add Application/Ongoing checks for Must(Not) Have Tags --- .../Private/Effects/GMCAbilityEffect.cpp | 10 ++++++---- .../Public/Effects/GMCAbilityEffect.h | 14 +++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index c8913181..8815d0f0 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -55,8 +55,10 @@ void UGMCAbilityEffect::StartEffect() bHasStarted = true; // Ensure tag requirements are met before applying the effect - if( ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) + if( ( EffectData.ApplicationMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustNotHaveTags) || + ( EffectData.OngoingMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.OngoingMustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.OngoingMustNotHaveTags) ) { EndEffect(); return; @@ -133,8 +135,8 @@ void UGMCAbilityEffect::Tick(float DeltaTime) TickEvent(DeltaTime); // Ensure tag requirements are met before applying the effect - if( ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) + if( ( EffectData.OngoingMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.OngoingMustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.OngoingMustNotHaveTags) ) { EndEffect(); } diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 4e2a44f0..1d039a32 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -98,13 +98,21 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedTags; + // Tags that the owner must have to apply this effect + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + FGameplayTagContainer ApplicationMustHaveTags; + + // Tags that the owner must not have to apply this effect + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + FGameplayTagContainer ApplicationMustNotHaveTags; + // Tags that the owner must have to apply and maintain this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - FGameplayTagContainer MustHaveTags; + FGameplayTagContainer OngoingMustHaveTags; // Tags that the owner must not have to apply and maintain this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - FGameplayTagContainer MustNotHaveTags; + FGameplayTagContainer OngoingMustNotHaveTags; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedAbilities; @@ -125,7 +133,7 @@ struct FGMCAbilityEffectData bool IsValid() { return GrantedTags != FGameplayTagContainer() || GrantedAbilities != FGameplayTagContainer() || Modifiers.Num() > 0 - || MustHaveTags != FGameplayTagContainer() || MustNotHaveTags != FGameplayTagContainer(); + || OngoingMustHaveTags != FGameplayTagContainer() || OngoingMustNotHaveTags != FGameplayTagContainer(); } FString ToString() const{ From 244727863c3cc8106a7795dfd395affc810011a3 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 15 Jul 2024 20:47:18 -0700 Subject: [PATCH 06/63] Prevent effect from starting if tag requirements not met --- Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 8815d0f0..959d2f47 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -52,8 +52,6 @@ void UGMCAbilityEffect::InitializeEffect(FGMCAbilityEffectData InitializationDat void UGMCAbilityEffect::StartEffect() { - bHasStarted = true; - // Ensure tag requirements are met before applying the effect if( ( EffectData.ApplicationMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustHaveTags) ) || DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustNotHaveTags) || @@ -63,6 +61,8 @@ void UGMCAbilityEffect::StartEffect() EndEffect(); return; } + + bHasStarted = true; AddTagsToOwner(); AddAbilitiesToOwner(); From c145b5e480d211d12abb4454da0eb055d0a07bcc Mon Sep 17 00:00:00 2001 From: utf8decodeerror <30543450+utf8decodeerror@users.noreply.github.com> Date: Thu, 15 Aug 2024 01:58:06 -0400 Subject: [PATCH 07/63] Virtualize ability effect (#90) * Ensure Unbound attributes are replicated properly at startup. * virtualize lifecycle function so that AbilityEffect can be subclassed * remove extra whitespace --------- Co-authored-by: Rachel Blackman Co-authored-by: utf8 --- .../Public/Effects/GMCAbilityEffect.h | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 1d039a32..ce6f4455 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -158,16 +158,16 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject FGMCAbilityEffectData EffectData; UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") - void InitializeEffect(FGMCAbilityEffectData InitializationData); + virtual void InitializeEffect(FGMCAbilityEffectData InitializationData); - void EndEffect(); + virtual void EndEffect(); virtual void Tick(float DeltaTime); UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Effect Tick"), Category="GMCAbilitySystem") void TickEvent(float DeltaTime); - void PeriodTick(); + virtual void PeriodTick(); void UpdateState(EEffectState State, bool Force=false); @@ -186,14 +186,6 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") UGMC_AbilitySystemComponent* OwnerAbilityComponent; -private: - bool bHasStarted; - - // Used for calculating when to tick Period effects - float PrevPeriodMod = 0; - - void CheckState(); - // Tags void AddTagsToOwner(); void RemoveTagsFromOwner(); @@ -207,8 +199,15 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool DuplicateEffectAlreadyApplied(); // Apply the things that should happen as soon as an effect starts. Tags, instant effects, etc. - void StartEffect(); + virtual void StartEffect(); + bool bHasStarted; + +private: + // Used for calculating when to tick Period effects + float PrevPeriodMod = 0; + + void CheckState(); public: FString ToString() { From 1af149109001def574f051550a4f995857e4a77a Mon Sep 17 00:00:00 2001 From: Peter Gilbert <33180578+petegilb@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:46:16 -0700 Subject: [PATCH 08/63] Rebased Deep Worlds fork (#93) * Add UPROPERTY Categories to allow compilation as engine plugin. * Adding Method CancelAbility, allowing ending an ability without triggering EndAbilityEvent. Internally, FinishEndAbility Method as also been added and ensure logics (see GAS termination of an ability) * Moving Activation tag requirement check before instantiation of the ability. * Added Activity Blocked by ActiveAbility * Fixing crash in HandleTaskHeartBeat. * Change behavior for attribute ChangeCallback - Callback OnAttributeChanged is now also called on SP/AP - Callback is now also called when the value is affected outside for whatever reason (like by SetAttributeValueByTag) - Support Replay - SetAttributeValueByTag now also re-caculate the value. Signed-off-by: Eric Rajot * BL-279 Clean before merge - Removed Log message - Removed irrelevant bounding Signed-off-by: Eric Rajot * BL-279 Bug fixing and improvement for rep notify * BL-279 Fixing Attribute notification * BL-279 Adding Byte data type to Set Target for DW GMC Ability * Improvement for tags, added duration * BL-232 Progress * BL-232 Initial work on External Effect/Ability Pending and Tag application * BL-232 Working state. * BL-232 Refactor and cleaning, handled now by Instanced Struct * BL-232 Progress of the day * Fixing a bug in remove effect. They are now removing by specifique ID when outer removed, to ensure the list rest coherent client <-> server * BL-232 Fixing removing effect * BL-232 bug fixing in effect * Bug fixing, adding accessor * BL-232 Fix effect remove itself even is another instance is running * Added getter * Stability - Fixed name space for SetTargetDataFloat, - Fixed EEffectType of GMCAbilityEffect sharing same name than playfab - Fixed wait for input key release with suggestion of Nas - GetAbilityMapData now return a const ref. For compability, you probably want to add to core redirect those lines : ``` +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectType",NewName="/Script/GMCAbilitySystem.EGMASEffectType") +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectState",NewName="/Script/GMCAbilitySystem.EGMASEffectState") ``` * Adding possibility for effect to end an active ability * Changing Ability Blocking way. They are now internally stored. An Ability store itself what ability it will block, instead of other ability who will block him. This allow to modify during execution this behavior. For example, you may be want to stop an ability activation only during X time in your ability execution. * Adding a nicer way to end ability * BL-225 Grenade 100% * Fix clang error * Adding Attribute Dynamic Condition to effect. * Fix crash when an active effect has been destroyed * Module upload * GMC Update * Addition of levitation actor * Crash fix * GMC Fix for starting abilities, Fix for stamina, Fix for crash, Adding speed for orb, Adding plugin for metahuman hairs. * Update for log * Fix for GetActiveEffect ? * Typo fix * couple of misc fixes from rebasing deep worlds fork --------- Signed-off-by: Eric Rajot Co-authored-by: Eric Rajot --- .../Private/Ability/GMCAbility.cpp | 37 +- .../Ability/Tasks/GMCAbilityTaskBase.cpp | 1 + .../Ability/Tasks/SetTargetDataByte.cpp | 44 ++ .../Ability/Tasks/WaitForInputKeyRelease.cpp | 11 +- .../Components/GMCAbilityComponent.cpp | 407 ++++++++++++++++-- .../Private/Effects/GMCAbilityEffect.cpp | 93 +++- .../Public/Ability/GMCAbility.h | 19 +- .../Public/Ability/GMCAbilityMapData.h | 2 +- .../Public/Ability/Tasks/SetTargetDataByte.h | 40 ++ .../Public/Ability/Tasks/SetTargetDataFloat.h | 2 +- .../Ability/Tasks/WaitForInputKeyRelease.h | 4 +- .../Public/Attributes/GMCAttributeClamp.h | 6 +- .../Public/Attributes/GMCAttributes.h | 5 + .../Public/Components/GMCAbilityComponent.h | 89 +++- .../Components/GMCAbilityOuterApplication.h | 86 ++++ .../Public/Effects/GMCAbilityEffect.h | 60 ++- 16 files changed, 827 insertions(+), 79 deletions(-) create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h create mode 100644 Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 7435df36..f1ebb91e 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -42,6 +42,11 @@ void UGMCAbility::Tick(float DeltaTime) return; } } + + if (bEndPending) { + EndAbility(); + return; + } TickTasks(DeltaTime); TickEvent(DeltaTime); @@ -125,6 +130,23 @@ void UGMCAbility::RemoveAbilityCost(){ } } + +void UGMCAbility::ModifyBlockOtherAbility(FGameplayTagContainer TagToAdd, FGameplayTagContainer TagToRemove) { + for (auto Tag : TagToAdd) { + BlockOtherAbility.AddTag(Tag); + } + + for (auto Tag : TagToRemove) { + BlockOtherAbility.RemoveTag(Tag); + } +} + + +void UGMCAbility::ResetBlockOtherAbility() { + BlockOtherAbility = GetClass()->GetDefaultObject()->BlockOtherAbility; +} + + void UGMCAbility::HandleTaskData(int TaskID, FInstancedStruct TaskData) { const FGMCAbilityTaskData TaskDataFromInstance = TaskData.Get(); @@ -139,7 +161,7 @@ void UGMCAbility::HandleTaskData(int TaskID, FInstancedStruct TaskData) void UGMCAbility::HandleTaskHeartbeat(int TaskID) { - if (RunningTasks.Contains(TaskID)) + if (RunningTasks.Contains(TaskID) && RunningTasks[TaskID] != nullptr) // Do we ever remove orphans tasks ? { RunningTasks[TaskID]->Heartbeat(); } @@ -150,6 +172,12 @@ void UGMCAbility::ServerConfirm() bServerConfirmed = true; } + +void UGMCAbility::SetPendingEnd() { + bEndPending = true; +} + + UGameplayTasksComponent* UGMCAbility::GetGameplayTasksComponent(const UGameplayTask& Task) const { if (OwnerAbilityComponent != nullptr) { return OwnerAbilityComponent; } @@ -210,7 +238,6 @@ bool UGMCAbility::IsOnCooldown() const } - bool UGMCAbility::PreExecuteCheckEvent_Implementation() { return true; } @@ -241,6 +268,12 @@ bool UGMCAbility::PreBeginAbility() { void UGMCAbility::BeginAbility() { + if (OwnerAbilityComponent->IsAbilityTagBlocked(AbilityTag)) { + CancelAbility(); + return; + } + + if (bApplyCooldownAtAbilityBegin) { CommitAbilityCooldown(); diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp index bb862823..798eb8c1 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp @@ -46,6 +46,7 @@ void UGMCAbilityTaskBase::Tick(float DeltaTime) { UE_LOG(LogGMCReplication, Error, TEXT("Server Task Heartbeat Timeout, Cancelling Ability: %s"), *Ability->GetName()); Ability->EndAbility(); + EndTask(); } } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp new file mode 100644 index 00000000..79883141 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp @@ -0,0 +1,44 @@ +#include "Ability/Tasks/SetTargetDataByte.h" + +#include "GMCAbilityComponent.h" + + +UGMCAbilityTask_SetTargetDataByte* UGMCAbilityTask_SetTargetDataByte::SetTargetDataByte(UGMCAbility* OwningAbility, + uint8 Byte) +{ + UGMCAbilityTask_SetTargetDataByte* Task = NewAbilityTask(OwningAbility); + Task->Ability = OwningAbility; + Task->Target = Byte; + return Task; +} + +void UGMCAbilityTask_SetTargetDataByte::Activate() +{ + Super::Activate(); + + if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + { + ClientProgressTask(); + } +} + +void UGMCAbilityTask_SetTargetDataByte::ProgressTask(FInstancedStruct& TaskData) +{ + Super::ProgressTask(TaskData); + const FGMCAbilityTaskTargetDataByte Data = TaskData.Get(); + + Completed.Broadcast(Data.Target); + EndTask(); +} + +void UGMCAbilityTask_SetTargetDataByte::ClientProgressTask() +{ + FGMCAbilityTaskTargetDataByte TaskData; + TaskData.TaskType = EGMCAbilityTaskDataType::Progress; + TaskData.AbilityID = Ability->GetAbilityID(); + TaskData.TaskID = TaskID; + TaskData.Target = Target; + const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData); + + Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance); +} diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index dfe36c2b..d2184dc7 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -1,6 +1,7 @@ #include "Ability/Tasks/WaitForInputKeyRelease.h" #include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" #include "Components/GMCAbilityComponent.h" #include "Kismet/KismetSystemLibrary.h" @@ -29,9 +30,15 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Activate() // Check that the value isn't currently false. if (bShouldCheckForReleaseDuringActivation) { - const FInputActionValue ActionValue = InputComponent->GetBoundActionValue(Ability->AbilityInputAction); - if (!ActionValue.Get()) + FInputActionValue ActionValue = FInputActionValue(); + APlayerController* PC = AbilitySystemComponent->GetOwner()->GetInstigatorController(); + if (UEnhancedInputLocalPlayerSubsystem* InputSubSystem = ULocalPlayer::GetSubsystem(PC->GetLocalPlayer())) { + ActionValue = InputSubSystem->GetPlayerInput() ? InputSubSystem->GetPlayerInput()->GetActionValue(Ability->AbilityInputAction) : FInputActionValue(); + } + + if (ActionValue.GetMagnitude() == 0) { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("UGMCAbilityTask_WaitForInputKeyRelease::Activate: EndOnStart!")); // We'll want to immediately unbind the binding. InputComponent->RemoveActionBindingForHandle(Binding.GetHandle()); InputBindingHandle = -1; diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 4bba1908..f1c5e361 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -10,6 +10,7 @@ #include "Ability/GMCAbilityMapData.h" #include "Attributes/GMCAttributesData.h" #include "Effects/GMCAbilityEffect.h" +#include "Kismet/KismetSystemLibrary.h" #include "Net/UnrealNetwork.h" // Sets default values for this component's properties @@ -168,12 +169,25 @@ void UGMC_AbilitySystemComponent::BindReplicationData() EGMC_CombineMode::CombineIfUnchanged, EGMC_SimulationMode::PeriodicAndOnChange_Output, EGMC_InterpolationFunction::TargetValue); + + GMCMovementComponent->BindInstancedStruct(AcknowledgeId, + EGMC_PredictionMode::ClientAuth_Input, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::None, + EGMC_InterpolationFunction::TargetValue); } void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsCombinedClientMove) { + OnAncillaryTick.Broadcast(DeltaTime); + + ClientHandlePendingEffect(); + ServerHandlePendingEffect(DeltaTime); + CheckActiveTagsChanged(); + CheckAttributeChanged(); + TickActiveEffects(DeltaTime); TickActiveCooldowns(DeltaTime); TickAncillaryActiveAbilities(DeltaTime); @@ -188,8 +202,36 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb SendTaskDataToActiveAbility(false); ClearAbilityAndTaskData(); + bInGMCTime = false; +} + + +TArray UGMC_AbilitySystemComponent::GetActivesEffectByTag(FGameplayTag GameplayTag) const { + TArray ActiveEffectsFound; + + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Searching for Active Effects with Tag: %s"), *GameplayTag.ToString()); + + for (const TTuple& EffectFound : ActiveEffects) { + if (IsValid(EffectFound.Value) && EffectFound.Value->EffectData.EffectTag.MatchesTag(GameplayTag)) { + ActiveEffectsFound.Add(EffectFound.Value); + } + } + + return ActiveEffectsFound; } + +UGMCAbilityEffect* UGMC_AbilitySystemComponent::GetFirstActiveEffectByTag(FGameplayTag GameplayTag) const { + for (auto& EffectFound : ActiveEffects) { + if (EffectFound.Value && EffectFound.Value->EffectData.EffectTag.MatchesTag(GameplayTag)) { + return EffectFound.Value; + } + } + + return nullptr; +} + + void UGMC_AbilitySystemComponent::AddAbilityMapData(UGMCAbilityMapData* AbilityMapData) { for (const FAbilityMapData& Data : AbilityMapData->GetAbilityMapData()) @@ -300,7 +342,7 @@ TArray UGMC_AbilitySystemComponent::GetActiveTagsByParentTag(const void UGMC_AbilitySystemComponent::TryActivateAbilitiesByInputTag(const FGameplayTag& InputTag, const UInputAction* InputAction, bool bFromMovementTick) { - for (const TSubclassOf ActivatedAbility : GetGrantedAbilitiesByTag(InputTag)) + for (const TSubclassOf& ActivatedAbility : GetGrantedAbilitiesByTag(InputTag)) { const UGMCAbility* AbilityCDO = ActivatedAbility->GetDefaultObject(); if(AbilityCDO && bFromMovementTick == AbilityCDO->bActivateOnMovementTick){ @@ -336,6 +378,12 @@ bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOfAbilityState != EAbilityState::Ended) + { + for (auto& Tag : ActiveAbility.Value->BlockOtherAbility) { + if (Tag.MatchesTag(AbilityTag)) { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability can't activate, blocked by Ability: %s"), *ActiveAbility.Value->GetName()); + return true; + } + } + } + } + + return false; +} + + int UGMC_AbilitySystemComponent::EndAbilitiesByTag(FGameplayTag AbilityTag) { int AbilitiesEnded = 0; for (const auto& ActiveAbilityData : ActiveAbilities) { if (ActiveAbilityData.Value->AbilityTag.MatchesTag(AbilityTag)) { - ActiveAbilityData.Value->EndAbility(); + ActiveAbilityData.Value->SetPendingEnd(); AbilitiesEnded++; } } @@ -455,6 +521,12 @@ float UGMC_AbilitySystemComponent::GetCooldownForAbility(const FGameplayTag Abil return 0.f; } + +float UGMC_AbilitySystemComponent::GetMaxCooldownForAbility(TSubclassOf Ability) const { + return Ability ? Ability.GetDefaultObject()->CooldownTime : 0.f; +} + + TMap UGMC_AbilitySystemComponent::GetCooldownsForInputTag(const FGameplayTag InputTag) { TArray> Abilities = GetGrantedAbilitiesByTag(InputTag); @@ -494,18 +566,7 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) bJustTeleported = false; ActionTimer += DeltaTime; - // Startup Effects - // Only applied on server. There's large desync if client tries to predict this, so just let server apply - // and reconcile. - if (HasAuthority() && StartingEffects.Num() > 0) - { - for (const TSubclassOf Effect : StartingEffects) - { - ApplyAbilityEffect(Effect, FGMCAbilityEffectData{}); - } - StartingEffects.Empty(); - } - + ApplyStartingEffects(); TickActiveAbilities(DeltaTime); @@ -526,6 +587,7 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) void UGMC_AbilitySystemComponent::GenSimulationTick(float DeltaTime) { CheckActiveTagsChanged(); + CheckAttributeChanged(); if (GMCMovementComponent->GetSmoothingTargetIdx() == -1) return; const FVector TargetLocation = GMCMovementComponent->MoveHistory[GMCMovementComponent->GetSmoothingTargetIdx()].OutputState.ActorLocation.Read(); @@ -547,6 +609,8 @@ void UGMC_AbilitySystemComponent::PreLocalMoveExecution() { TaskData = QueuedTaskData.Pop(); } + + } void UGMC_AbilitySystemComponent::BeginPlay() @@ -585,6 +649,12 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() // FFastArraySerializer will duplicate all attributes on first replication if we // add the attributes on the clients as well. UnBoundAttributes.AddAttribute(NewAttribute); + + } + + if (!AttributeData.bGMCBound) { + // Initiate old unbound attributes + OldUnBoundAttributes.AddAttribute(NewAttribute); } } } @@ -603,6 +673,14 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() UnBoundAttributes.MarkItemDirty(Attribute); } UnBoundAttributes.MarkArrayDirty(); + + + for (const FAttribute& Attribute : OldUnBoundAttributes.Items) + { + Attribute.CalculateValue(); + } + + OldBoundAttributes = BoundAttributes; } void UGMC_AbilitySystemComponent::SetStartingTags() @@ -658,6 +736,22 @@ void UGMC_AbilitySystemComponent::CheckActiveTagsChanged() } } + +void UGMC_AbilitySystemComponent::CheckAttributeChanged() { + // Check Bound Attributes + for (int i = 0; i < BoundAttributes.Attributes.Num(); i++) + { + FAttribute& Attribute = BoundAttributes.Attributes[i]; + FAttribute& OldAttribute = OldBoundAttributes.Attributes[i]; + if (Attribute.Value != OldAttribute.Value) + { + OnAttributeChanged.Broadcast(Attribute.Tag, OldAttribute.Value, Attribute.Value); + OldAttribute.Value = Attribute.Value; + } + } +} + + void UGMC_AbilitySystemComponent::CleanupStaleAbilities() { for (auto It = ActiveAbilities.CreateIterator(); It; ++It) @@ -684,15 +778,22 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime) // Tick Effects for (const TPair& Effect : ActiveEffects) { + + if (!IsValid(Effect.Value)) { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Active Effect id %d is null or pending kill, removing from the list."), Effect.Key); + CompletedActiveEffects.Push(Effect.Key); + continue; + } + Effect.Value->Tick(DeltaTime); if (Effect.Value->bCompleted) {CompletedActiveEffects.Push(Effect.Key);} // Check for predicted effects that have not been server confirmed - if (!HasAuthority() && + if (!HasAuthority() && ProcessedEffectIDs.Contains(Effect.Key) && !ProcessedEffectIDs[Effect.Key] && Effect.Value->ClientEffectApplicationTime + ClientEffectApplicationTimeout < ActionTimer) { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect Not Confirmed By Server: %d, Removing..."), Effect.Key); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect `%s` Not Confirmed By Server (ID: `%d`), Removing..."), *GetNameSafe(Effect.Value), Effect.Key); Effect.Value->EndEffect(); CompletedActiveEffects.Push(Effect.Key); } @@ -773,6 +874,119 @@ void UGMC_AbilitySystemComponent::CheckRemovedEffects() } } +void UGMC_AbilitySystemComponent::AddPendingEffectApplications(FGMCOuterApplicationWrapper& Wrapper) { + check(HasAuthority()) + + Wrapper.ClientGraceTimeRemaining = 1.f; + Wrapper.LateApplicationID = GenerateLateApplicationID(); + + PendingApplicationServer.Add(Wrapper); + RPCClientAddPendingEffectApplication(Wrapper); +} + + +void UGMC_AbilitySystemComponent::RPCClientAddPendingEffectApplication_Implementation( + FGMCOuterApplicationWrapper Wrapper) { + PendingApplicationClient.Add(Wrapper); +} + + + +void UGMC_AbilitySystemComponent::ServerHandlePendingEffect(float DeltaTime) { + if (!HasAuthority()) { + return; + } + + FGMCAcknowledgeId& AckId = AcknowledgeId.GetMutable(); + + + for (int i = PendingApplicationServer.Num() - 1; i >= 0; i--) { + FGMCOuterApplicationWrapper& Wrapper = PendingApplicationServer[i]; + + if (Wrapper.ClientGraceTimeRemaining <= 0.f || AckId.Id.Contains(Wrapper.LateApplicationID)) { + + switch (Wrapper.Type) { + case EGMC_AddEffect: { + const FGMCOuterEffectAdd& Data = Wrapper.OuterApplicationData.Get(); + UGMCAbilityEffect* AbilityEffect = DuplicateObject(Data.EffectClass->GetDefaultObject(), this); + AbilityEffect->EffectData.EffectID = Wrapper.LateApplicationID; + FGMCAbilityEffectData EffectData = Data.InitializationData.IsValid() ? Data.InitializationData : AbilityEffect->EffectData; + UGMCAbilityEffect* FX = ApplyAbilityEffect(AbilityEffect, EffectData); + if (Wrapper.ClientGraceTimeRemaining <= 0.f) { + UE_LOG(LogGMCAbilitySystem, Log, TEXT("Client Add Effect `%s ` Missed Grace time, Force application : id: %d"), *GetNameSafe(Data.EffectClass), FX->EffectData.EffectID); + } + } break; + case EGMC_RemoveEffect: { + const FGMCOuterEffectRemove& Data = Wrapper.OuterApplicationData.Get(); + RemoveEffectById(Data.Ids); + if (Wrapper.ClientGraceTimeRemaining <= 0.f) { + UE_LOG(LogGMCAbilitySystem, Log, TEXT("Client Remove Effect Missed Grace time, Force remove")); + } + } break; + } + PendingApplicationServer.RemoveAt(i); + } + else { + Wrapper.ClientGraceTimeRemaining -= DeltaTime; + } + + + } + +} + + +void UGMC_AbilitySystemComponent::ClientHandlePendingEffect() { + + + for (int i = PendingApplicationClient.Num() - 1; i >= 0; i--) + { + FGMCOuterApplicationWrapper& LateApplicationData = PendingApplicationClient[i]; + + switch (LateApplicationData.Type) { + case EGMC_AddEffect: { + const FGMCOuterEffectAdd& Data = LateApplicationData.OuterApplicationData.Get(); + + if (Data.EffectClass == nullptr) { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("ClientHandlePendingEffect: EffectClass is null")); + break; + } + + UGMCAbilityEffect* CDO = Data.EffectClass->GetDefaultObject(); + + if (CDO == nullptr) { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("ClientHandlePendingEffect: CDO is null")); + break; + } + + UGMCAbilityEffect* AbilityEffect = DuplicateObject(CDO, this); + AbilityEffect->EffectData.EffectID = LateApplicationData.LateApplicationID; + FGMCAbilityEffectData EffectData = Data.InitializationData.IsValid() ? Data.InitializationData : AbilityEffect->EffectData; + ApplyAbilityEffect(AbilityEffect, EffectData); + } break; + case EGMC_RemoveEffect: { + const FGMCOuterEffectRemove& Data = LateApplicationData.OuterApplicationData.Get(); + RemoveEffectById(Data.Ids); + } break; + } + + PendingApplicationClient.RemoveAt(i); + AcknowledgeId.GetMutable().Id.Add(LateApplicationData.LateApplicationID); + } +} + + +int UGMC_AbilitySystemComponent::GenerateLateApplicationID() { + int NewEffectID = static_cast(ActionTimer * 100); + while (ActiveEffects.Contains(NewEffectID)) + { + NewEffectID++; + } + + return NewEffectID; +} + + void UGMC_AbilitySystemComponent::RPCTaskHeartbeat_Implementation(int AbilityID, int TaskID) { if (ActiveAbilities.Contains(AbilityID) && ActiveAbilities[AbilityID] != nullptr) @@ -808,6 +1022,24 @@ void UGMC_AbilitySystemComponent::RPCConfirmAbilityActivation_Implementation(int } } + +void UGMC_AbilitySystemComponent::ApplyStartingEffects(bool bForce) { + if (HasAuthority() && StartingEffects.Num() > 0 && (bForce || !bStartingEffectsApplied)) + { + for (const TSubclassOf& Effect : StartingEffects) + { + // Dont apply the same effect twice + if (!Algo::FindByPredicate(ActiveEffects, [Effect](const TPair& ActiveEffect) { + return IsValid(ActiveEffect.Value) && ActiveEffect.Value->GetClass() == Effect; + })) { + ApplyAbilityEffect(Effect, FGMCAbilityEffectData{}); + } + } + bStartingEffectsApplied = true; + } +} + + TArray> UGMC_AbilitySystemComponent::GetGrantedAbilitiesByTag(FGameplayTag AbilityTag) { if (!GrantedAbilityTags.HasTag(AbilityTag)) @@ -927,31 +1159,62 @@ void UGMC_AbilitySystemComponent::InitializeStartingAbilities() } } -void UGMC_AbilitySystemComponent::OnRep_UnBoundAttributes(FGMCUnboundAttributeSet PreviousAttributes) +void UGMC_AbilitySystemComponent::OnRep_UnBoundAttributes() { - const TArray& OldAttributes = PreviousAttributes.Items; + + if (OldUnBoundAttributes.Items.Num() != UnBoundAttributes.Items.Num()) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("OnRep_UnBoundAttributes: Mismatched Attribute Old != New Value !")); + } + + TArray& OldAttributes = OldUnBoundAttributes.Items; const TArray& CurrentAttributes = UnBoundAttributes.Items; - TMap OldValues; + TMap OldValues; + + // If this mitchmatch, that mean we need to reset the number of attributes + - for (const FAttribute& Attribute : OldAttributes){ - OldValues.Add(Attribute.Tag, Attribute.Value); + for (FAttribute& Attribute : OldAttributes){ + OldValues.Add(Attribute.Tag, &Attribute.Value); } - for (const FAttribute& Attribute : CurrentAttributes){ - if (OldValues.Contains(Attribute.Tag) && OldValues[Attribute.Tag] != Attribute.Value){ - OnAttributeChanged.Broadcast(Attribute.Tag, OldValues[Attribute.Tag], Attribute.Value); - NativeAttributeChangeDelegate.Broadcast(Attribute.Tag, OldValues[Attribute.Tag], Attribute.Value); + + for (const FAttribute& Attribute : CurrentAttributes){ + if (OldValues.Contains(Attribute.Tag) && *OldValues[Attribute.Tag] != Attribute.Value){ + NativeAttributeChangeDelegate.Broadcast(Attribute.Tag, *OldValues[Attribute.Tag], Attribute.Value); + OnAttributeChanged.Broadcast(Attribute.Tag, *OldValues[Attribute.Tag], Attribute.Value); UnBoundAttributes.MarkAttributeDirty(Attribute); + + // Update Old Value + *OldValues[Attribute.Tag] = Attribute.Value; } } + + } //BP Version -UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData) +UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData, bool bOuterActivation) { - if (Effect == nullptr) return nullptr; + if (Effect == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Trying to apply Effect, but effect is null!")); + return nullptr; + } + + + // We are trying to apply an effect from an outside source, so we will need to go trough a different routing to apply it + if (bOuterActivation) { + if (HasAuthority()) { + FGMCOuterApplicationWrapper Wrapper = FGMCOuterApplicationWrapper::Make(Effect, InitializationData); + AddPendingEffectApplications(Wrapper); + } + return nullptr; + } + + UGMCAbilityEffect* AbilityEffect = DuplicateObject(Effect->GetDefaultObject(), this); @@ -971,7 +1234,12 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfEndEffect(); } -int32 UGMC_AbilitySystemComponent::RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove){ - if(NumToRemove < -1 || !InEffectTag.IsValid()) return 0; +int32 UGMC_AbilitySystemComponent::RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove, bool bOuterActivation) { + + if (NumToRemove < -1 || !InEffectTag.IsValid()) { + return 0; + } + TMap EffectsToRemove; int32 NumRemoved = 0; - // Using this iterator allows us to remove while iterating + for(const TTuple Effect : ActiveEffects) { if(NumRemoved == NumToRemove){ break; } + if(Effect.Value->EffectData.EffectTag.IsValid() && Effect.Value->EffectData.EffectTag.MatchesTagExact(InEffectTag)){ - Effect.Value->EndEffect(); + EffectsToRemove.Add(Effect.Key, Effect.Value); NumRemoved++; } } + + + if (bOuterActivation) { + if (HasAuthority() && EffectsToRemove.Num() > 0) { + + TArray EffectIDsToRemove; + for (const auto& ToRemove : EffectsToRemove) { + EffectIDsToRemove.Add(ToRemove.Key); + } + + FGMCOuterApplicationWrapper Wrapper = FGMCOuterApplicationWrapper::Make(EffectIDsToRemove); + AddPendingEffectApplications(Wrapper); + } + return 0; + } + + for (auto& ToRemove : EffectsToRemove) { + ToRemove.Value->EndEffect(); + } + return NumRemoved; } + +bool UGMC_AbilitySystemComponent::RemoveEffectById(TArray Ids, bool bOuterActivation) { + + if (!Ids.Num()) { + return true; + } + + // check all IDs exists + for (int Id : Ids) { + if (!ActiveEffects.Contains(Id)) { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Trying to remove effect with ID %d, but it doesn't exist!"), Id); + return false; + } + } + + if (bOuterActivation) { + if (HasAuthority()) { + FGMCOuterApplicationWrapper Wrapper = FGMCOuterApplicationWrapper::Make(Ids); + AddPendingEffectApplications(Wrapper); + } + return true; + } + + for (auto& Effect : ActiveEffects) { + if (Ids.Contains(Effect.Key)) { + Effect.Value->EndEffect(); + } + } + + return true; +} + + int32 UGMC_AbilitySystemComponent::GetNumEffectByTag(FGameplayTag InEffectTag){ if(!InEffectTag.IsValid()) return -1; int32 Count = 0; @@ -1089,6 +1415,16 @@ float UGMC_AbilitySystemComponent::GetAttributeValueByTag(const FGameplayTag Att return 0; } + +FAttributeClamp UGMC_AbilitySystemComponent::GetAttributeClampByTag(FGameplayTag AttributeTag) const { + if (const FAttribute* Att = GetAttributeByTag(AttributeTag)) + { + return Att->Clamp; + } + return FAttributeClamp(); +} + + bool UGMC_AbilitySystemComponent::SetAttributeValueByTag(FGameplayTag AttributeTag, float NewValue, bool bResetModifiers) { if (const FAttribute* Att = GetAttributeByTag(AttributeTag)) @@ -1099,7 +1435,8 @@ bool UGMC_AbilitySystemComponent::SetAttributeValueByTag(FGameplayTag AttributeT { Att->ResetModifiers(); } - + + Att->CalculateValue(); return true; } return false; @@ -1171,6 +1508,7 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi // If we are unbound that means we shouldn't predict. if(!AffectedAttribute->bIsGMCBound && !HasAuthority()) return; float OldValue = AffectedAttribute->Value; + FGMCUnboundAttributeSet OldUnboundAttributes = UnBoundAttributes; if (bNegateValue) { @@ -1187,6 +1525,9 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi BoundAttributes.MarkAttributeDirty(*AffectedAttribute); UnBoundAttributes.MarkAttributeDirty(*AffectedAttribute); + if (!AffectedAttribute->bIsGMCBound) { + OnRep_UnBoundAttributes(); + } } } diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 959d2f47..f2d0f280 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -5,6 +5,7 @@ #include "GMCAbilitySystem.h" #include "Components/GMCAbilityComponent.h" +#include "Kismet/KismetSystemLibrary.h" void UGMCAbilityEffect::InitializeEffect(FGMCAbilityEffectData InitializationData) @@ -55,8 +56,8 @@ void UGMCAbilityEffect::StartEffect() // Ensure tag requirements are met before applying the effect if( ( EffectData.ApplicationMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustHaveTags) ) || DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustNotHaveTags) || - ( EffectData.OngoingMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.OngoingMustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.OngoingMustNotHaveTags) ) + ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) { EndEffect(); return; @@ -66,6 +67,7 @@ void UGMCAbilityEffect::StartEffect() AddTagsToOwner(); AddAbilitiesToOwner(); + EndActiveAbilitiesFromOwner(); // Instant effects modify base value and end instantly if (EffectData.bIsInstant) @@ -100,18 +102,19 @@ void UGMCAbilityEffect::StartEffect() EndEffect(); } - UpdateState(EEffectState::Started, true); + UpdateState(EGMASEffectState::Started, true); } + void UGMCAbilityEffect::EndEffect() { // Prevent EndEffect from being called multiple times if (bCompleted) return; bCompleted = true; - if (CurrentState != EEffectState::Ended) + if (CurrentState != EGMASEffectState::Ended) { - UpdateState(EEffectState::Ended, true); + UpdateState(EGMASEffectState::Ended, true); } // Only remove tags and abilities if the effect has started @@ -129,21 +132,48 @@ void UGMCAbilityEffect::EndEffect() RemoveAbilitiesFromOwner(); } + +void UGMCAbilityEffect::BeginDestroy() { + + + // This is addition is mostly to catch ghost effect who are still in around. + // it's a bug, and ideally should not happen but that happen. a check in engine is added to catch this, and an error log for packaged game. + /*if (OwnerAbilityComponent) { + for (TTuple Effect : OwnerAbilityComponent->GetActiveEffects()) + { + if (Effect.Value == this) { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect %s is still in the active effect list of %s"), *Effect.Value->EffectData.EffectTag.ToString(), *OwnerAbilityComponent->GetOwner()->GetName()); + + if (!bCompleted) { + UE_LOG( LogGMCAbilitySystem, Error, TEXT("Effect %s is being destroyed without being completed"), *Effect.Value->EffectData.EffectTag.ToString()); + EndEffect(); + } + + Effect.Value = nullptr; + } + } + }*/ + + UObject::BeginDestroy(); +} + + void UGMCAbilityEffect::Tick(float DeltaTime) { if (bCompleted) return; + EffectData.CurrentDuration += DeltaTime; TickEvent(DeltaTime); // Ensure tag requirements are met before applying the effect - if( ( EffectData.OngoingMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.OngoingMustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.OngoingMustNotHaveTags) ) + if( ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) { EndEffect(); } // If there's a period, check to see if it's time to tick - if (!IsPeriodPaused() && EffectData.Period > 0 && CurrentState == EEffectState::Started) + if (!IsPeriodPaused() && EffectData.Period > 0 && CurrentState == EGMASEffectState::Started) { const float Mod = FMath::Fmod(OwnerAbilityComponent->ActionTimer, EffectData.Period); if (Mod < PrevPeriodMod) @@ -160,17 +190,25 @@ void UGMCAbilityEffect::TickEvent_Implementation(float DeltaTime) { } + +bool UGMCAbilityEffect::AttributeDynamicCondition_Implementation() const { + return true; +} + + void UGMCAbilityEffect::PeriodTick() { - for (const FGMCAttributeModifier& AttributeModifier : EffectData.Modifiers) - { - OwnerAbilityComponent->ApplyAbilityEffectModifier(AttributeModifier, true); + if (AttributeDynamicCondition()) { + for (const FGMCAttributeModifier& AttributeModifier : EffectData.Modifiers) + { + OwnerAbilityComponent->ApplyAbilityEffectModifier(AttributeModifier, true); + } } } -void UGMCAbilityEffect::UpdateState(EEffectState State, bool Force) +void UGMCAbilityEffect::UpdateState(EGMASEffectState State, bool Force) { - if (State == EEffectState::Ended) + if (State == EGMASEffectState::Ended) { // UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Effect Ended")); } @@ -191,8 +229,17 @@ void UGMCAbilityEffect::AddTagsToOwner() } } -void UGMCAbilityEffect::RemoveTagsFromOwner() +void UGMCAbilityEffect::RemoveTagsFromOwner(bool bPreserveOnMultipleInstances) { + + if (bPreserveOnMultipleInstances && EffectData.EffectTag.IsValid()) { + TArray ActiveEffect = OwnerAbilityComponent->GetActivesEffectByTag(EffectData.EffectTag); + + if (ActiveEffect.Num() > 1) { + return; + } + } + for (const FGameplayTag Tag : EffectData.GrantedTags) { OwnerAbilityComponent->RemoveActiveTag(Tag); @@ -215,6 +262,16 @@ void UGMCAbilityEffect::RemoveAbilitiesFromOwner() } } + +void UGMCAbilityEffect::EndActiveAbilitiesFromOwner() { + + for (const FGameplayTag Tag : EffectData.CancelAbilityOnActivation) + { + OwnerAbilityComponent->EndAbilitiesByTag(Tag); + } +} + + bool UGMCAbilityEffect::DoesOwnerHaveTagFromContainer(FGameplayTagContainer& TagContainer) const { for (const FGameplayTag Tag : TagContainer) @@ -249,20 +306,20 @@ void UGMCAbilityEffect::CheckState() { switch (CurrentState) { - case EEffectState::Initialized: + case EGMASEffectState::Initialized: if (OwnerAbilityComponent->ActionTimer >= EffectData.StartTime) { StartEffect(); - UpdateState(EEffectState::Started, true); + UpdateState(EGMASEffectState::Started, true); } break; - case EEffectState::Started: + case EGMASEffectState::Started: if (EffectData.Duration != 0 && OwnerAbilityComponent->ActionTimer >= EffectData.EndTime) { EndEffect(); } break; - case EEffectState::Ended: + case EGMASEffectState::Ended: break; default: break; } diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index 7b0dbebb..ce52bd2e 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -158,6 +158,14 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") virtual void RemoveAbilityCost(); + // Live modifying the BlockOtherAbility tags + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") + virtual void ModifyBlockOtherAbility(FGameplayTagContainer TagToAdd, FGameplayTagContainer TagToRemove); + + // Reset the BlockOtherAbility tags to the default values + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") + virtual void ResetBlockOtherAbility(); + // GMC_AbilitySystemComponent that owns this ability UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") UGMC_AbilitySystemComponent* OwnerAbilityComponent; @@ -189,10 +197,14 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn // Tags that the owner must not have to activate ability. BeginAbility will not be called if the owner has these tags. FGameplayTagContainer ActivationBlockedTags; - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(Categories="Ability")) // Cancel Abilities with these tags when this ability is activated FGameplayTagContainer CancelAbilitiesWithTag; + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(Categories="Ability")) + // Prevent Abilities with these tags from activating when this ability is activated + FGameplayTagContainer BlockOtherAbility; + /** * If true, activate on movement tick, if false, activate on ancillary tick. Defaults to true. * Should be set to false for actions that should not be replayed on mispredictions. i.e. firing a weapon @@ -202,6 +214,9 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UFUNCTION() void ServerConfirm(); + + UFUNCTION() + void SetPendingEnd(); // -------------------------------------- // IGameplayTaskOwnerInterface @@ -222,6 +237,8 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn bool bServerConfirmed = false; + bool bEndPending = false; + float ClientStartTime; // How long to wait for server to confirm ability before cancelling on client diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h index e2aa8e06..6dd93d84 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h @@ -37,5 +37,5 @@ class GMCABILITYSYSTEM_API UGMCAbilityMapData : public UPrimaryDataAsset{ TArray AbilityMapData; public: - TArray GetAbilityMapData() {return AbilityMapData;} + const TArray& GetAbilityMapData() const { return AbilityMapData; } }; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h new file mode 100644 index 00000000..9a004b9e --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h @@ -0,0 +1,40 @@ +#pragma once +#include "GMCAbilityTaskBase.h" +#include "GMCAbilityTaskData.h" +#include "Ability/GMCAbility.h" +#include "LatentActions.h" +#include "Engine/CancellableAsyncAction.h" +#include "SetTargetDataByte.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUGMCAbilityTaskTargetDataByteAsyncActionPin, uint8, Target); + +USTRUCT(BlueprintType) +struct FGMCAbilityTaskTargetDataByte : public FGMCAbilityTaskData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + uint8 Target{0}; +}; + +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataByte : public UGMCAbilityTaskBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FUGMCAbilityTaskTargetDataByteAsyncActionPin Completed; + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + uint8 Target; + + virtual void ProgressTask(FInstancedStruct& TaskData) override; + virtual void ClientProgressTask() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Byte)",Category = "GMCAbilitySystem/Tasks") + static UGMCAbilityTask_SetTargetDataByte* SetTargetDataByte(UGMCAbility* OwningAbility, uint8 Byte); + + //Overriding BP async action base + virtual void Activate() override; +}; \ No newline at end of file diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h index 92d8c7ae..d8a6f909 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h @@ -18,7 +18,7 @@ struct FGMCAbilityTaskTargetDataFloat : public FGMCAbilityTaskData }; UCLASS() -class UGMCAbilityTask_SetTargetDataFloat : public UGMCAbilityTaskBase +class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataFloat : public UGMCAbilityTaskBase { GENERATED_BODY() diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h index d237eb73..450ce726 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h @@ -29,7 +29,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA * @return */ UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Release",Category = "GMCAbilitySystem/Tasks") - static UGMCAbilityTask_WaitForInputKeyRelease* WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation); + static UGMCAbilityTask_WaitForInputKeyRelease* WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation = true); //Overriding BP async action base virtual void Activate() override; @@ -43,7 +43,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA /** If true, we may complete this task during activation if the ability's input action key is already released. */ UPROPERTY(Transient) - bool bShouldCheckForReleaseDuringActivation; + bool bShouldCheckForReleaseDuringActivation = true; private: diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeClamp.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeClamp.h index 79f3e24f..845bfbe4 100644 --- a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeClamp.h +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeClamp.h @@ -5,13 +5,13 @@ class UGMC_AbilitySystemComponent; -USTRUCT() +USTRUCT(BlueprintType) struct GMCABILITYSYSTEM_API FAttributeClamp { GENERATED_BODY() // Minimum attribute value - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GMCAbilitySystem") float Min { 0.f }; // Value will be clamped to the value of this attribute @@ -20,7 +20,7 @@ struct GMCABILITYSYSTEM_API FAttributeClamp FGameplayTag MinAttributeTag { FGameplayTag::EmptyTag }; // Maximum attribute value - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GMCAbilitySystem") float Max { 0.f }; // Value will be clamped to the value of this attribute diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h index e8f7a7b7..bf79f365 100644 --- a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h @@ -5,6 +5,8 @@ #include "Net/Serialization/FastArraySerializer.h" #include "GMCAttributes.generated.h" +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAttributeChanged, float, OldValue, float, NewValue); + USTRUCT(BlueprintType) struct GMCABILITYSYSTEM_API FAttribute : public FFastArraySerializerItem { @@ -16,6 +18,9 @@ struct GMCABILITYSYSTEM_API FAttribute : public FFastArraySerializerItem CalculateValue(false); } + UPROPERTY(BlueprintAssignable) + FAttributeChanged OnAttributeChanged; + UPROPERTY() mutable float AdditiveModifier{0}; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index ea708ee1..cad75c50 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -11,6 +11,7 @@ #include "Ability/Tasks/GMCAbilityTaskData.h" #include "Effects/GMCAbilityEffect.h" #include "Components/ActorComponent.h" +#include "GMCAbilityOuterApplication.h" #include "GMCAbilityComponent.generated.h" @@ -27,6 +28,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAncillaryTick, float, DeltaTime); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActiveTagsChanged, FGameplayTagContainer, AddedTags, FGameplayTagContainer, RemovedTags); DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, const FGameplayTagContainer&, const FGameplayTagContainer&); + + USTRUCT() struct FEffectStatePrediction { @@ -52,6 +55,11 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Sets default values for this component's properties UGMC_AbilitySystemComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + // Will apply the starting effects and abilities to the component, + // bForce will re-apply the effects, usefull if we want to re-apply the effects after a reset (like a death) + // Must be called on the server only + virtual void ApplyStartingEffects(bool bForce = false); + // Bound/Synced over GMC UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") double ActionTimer; @@ -68,6 +76,14 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Return the active ability effects TMap GetActiveEffects() const { return ActiveEffects; } + // Return active Effect with tag + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + TArray GetActivesEffectByTag(FGameplayTag GameplayTag) const; + + // Get the first active effect with the Effecttag + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + UGMCAbilityEffect* GetFirstActiveEffectByTag(FGameplayTag GameplayTag) const; + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") void AddAbilityMapData(UGMCAbilityMapData* AbilityMapData); @@ -143,6 +159,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, DisplayName="Count Activated Ability Instances", Category="GMAS|Abilities") int32 GetActiveAbilityCount(TSubclassOf AbilityClass); + // Perform a check in every active ability against BlockOtherAbility and check if the tag provided is present + bool IsAbilityTagBlocked(const FGameplayTag AbilityTag) const; + UFUNCTION(BlueprintCallable, DisplayName="End Abilities (By Tag)", Category="GMAS|Abilities") // End all abilities with the corresponding tag, returns the number of abilities ended int EndAbilitiesByTag(FGameplayTag AbilityTag); @@ -163,6 +182,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintPure, Category = "GMCAbilitySystem") float GetCooldownForAbility(const FGameplayTag AbilityTag) const; + + UFUNCTION(BlueprintPure, Category = "GMCAbilitySystem") + float GetMaxCooldownForAbility(TSubclassOf Ability) const; // Get the cooldowns for all abilities associated with Input tag UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") @@ -183,12 +205,19 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") FGMCAttributeSet BoundAttributes; + /** Reminder to check attributes */ + UPROPERTY() + FGMCAttributeSet OldBoundAttributes; + /** Struct containing attributes that are replicated and unbound from the GMC */ UPROPERTY(ReplicatedUsing = OnRep_UnBoundAttributes, BlueprintReadOnly, Category = "GMCAbilitySystem") FGMCUnboundAttributeSet UnBoundAttributes; + UPROPERTY() + FGMCUnboundAttributeSet OldUnBoundAttributes; + UFUNCTION() - void OnRep_UnBoundAttributes(FGMCUnboundAttributeSet PreviousAttributes); + void OnRep_UnBoundAttributes(); /** * Applies an effect to the Ability Component @@ -200,10 +229,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo * @param bAppliedByServer Is this Effect only applied by server? Used to help client predict the unpredictable. */ UFUNCTION(BlueprintCallable, Category="GMAS|Effects", meta = (AutoCreateRefTerm = "AdditionalModifiers")) - UGMCAbilityEffect* ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData); + UGMCAbilityEffect* ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData, bool bOuterActivation = false); UGMCAbilityEffect* ApplyAbilityEffect(UGMCAbilityEffect* Effect, FGMCAbilityEffectData InitializationData); - + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") void RemoveActiveAbilityEffect(UGMCAbilityEffect* Effect); @@ -213,7 +242,14 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo * If the inputted count is higher than the number of active corresponding effects, remove all we can. */ UFUNCTION(BlueprintCallable, Category="GMAS|Effects") - int32 RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove=-1); + int32 RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove=-1, bool bOuterActivation = false); + + /** + * Removes an instanced effect by ids. + * return false if any of the ids are invalid. + */ + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + bool RemoveEffectById(TArray Ids, bool bOuterActivation = false); /** * Gets the number of active effects with the inputted tag. @@ -252,6 +288,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintPure, Category="GMAS|Attributes") float GetAttributeValueByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; + // Get Attribute value by Tag + UFUNCTION(BlueprintPure, Category="GMAS|Attributes") + FAttributeClamp GetAttributeClampByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; + // Set Attribute value by Tag // Will NOT trigger an "OnAttributeChanged" Event // bResetModifiers: Will reset all modifiers on the attribute to the base value. DO NOT USE if you have any active effects that modify this attribute. @@ -375,6 +415,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void SendTaskDataToActiveAbility(bool bFromMovement); private: + + bool bStartingEffectsApplied = false; + // Array of data objects to initialize the component's ability map UPROPERTY(EditDefaultsOnly, Category="Ability") TArray> AbilityMaps; @@ -407,9 +450,13 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo TArray QueuedEffectStates; + + UPROPERTY() TMap ActiveAbilities; + + UPROPERTY() TMap ActiveCooldowns; @@ -424,6 +471,11 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Check if ActiveTags has changed and call delegates void CheckActiveTagsChanged(); + + // Check if any Attribute has changed and call delegates + void CheckAttributeChanged(); + + void CheckAttributeChanged_Internal(FGMCAttributeSet& OldAttributeSet, FGMCAttributeSet& NewAttributeSet); // Clear out abilities in the Ended state from the ActivateAbilities map void CleanupStaleAbilities(); @@ -459,6 +511,35 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY() TMap ActiveEffects; + // Effect applied externally, pending activation, used by server and client. Not replicated. + //TODO: Later we will need to encapsulate this with Instanced struct to have a more generic way to handle this, and have cohabitation server <-> client + UPROPERTY() + TArray PendingApplicationServer; + + UPROPERTY() + TArray PendingApplicationClient; + + // doesn't work ATM. + UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem", meta=(AllowPrivateAccess="true")) + bool bInGMCTime = false; + + // TODO: Need to be pushed later on a int64 32 index + 32 bitfield + // Binded Used for acknowledge server initiated ability/effect + FInstancedStruct AcknowledgeId = FInstancedStruct::Make(FGMCAcknowledgeId{}); + + void AddPendingEffectApplications(FGMCOuterApplicationWrapper& Wrapper); + // Let the client know that the server ask for an external effect application + UFUNCTION(Client, Reliable) + void RPCClientAddPendingEffectApplication(FGMCOuterApplicationWrapper Wrapper); + + void ServerHandlePendingEffect(float DeltaTime); + + void ClientHandlePendingEffect(); + + int GenerateLateApplicationID(); + + int LateApplicationIDCounter = 0; + // Effect IDs that have been processed and don't need to be remade when ActiveEffectsData is replicated // This need to be persisted for a while // This never empties out so it'll infinitely grow, probably a better way to accomplish this diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h new file mode 100644 index 00000000..85bffac5 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h @@ -0,0 +1,86 @@ +#pragma once + +#include "CoreMinimal.h" +#include "InstancedStruct.h" +#include "GMCAbilityOuterApplication.generated.h" + +USTRUCT() +struct FGMCAcknowledgeId { + GENERATED_BODY() + + UPROPERTY() + TArray Id = {}; +}; + + +UENUM() +enum EGMCOuterApplicationType { + EGMC_AddEffect, + EGMC_RemoveEffect, +}; + +USTRUCT(BlueprintType) +struct FGMCOuterEffectAdd { + GENERATED_BODY() + + UPROPERTY() + TSubclassOf EffectClass; + + UPROPERTY() + FGMCAbilityEffectData InitializationData; +}; + +USTRUCT(BlueprintType) +struct FGMCOuterEffectRemove { + GENERATED_BODY() + + UPROPERTY() + TArray Ids = {}; +}; + +USTRUCT(BlueprintType) +struct FGMCOuterApplicationWrapper { + GENERATED_BODY() + + UPROPERTY() + TEnumAsByte Type = EGMC_AddEffect; + + UPROPERTY() + FInstancedStruct OuterApplicationData; + + UPROPERTY() + int LateApplicationID = 0; + + float ClientGraceTimeRemaining = 0.f; + + template + static FGMCOuterApplicationWrapper Make(Args... args) + { + FGMCOuterApplicationWrapper Wrapper; + Wrapper.OuterApplicationData = FInstancedStruct::Make(args...); + return Wrapper; + } + + +}; + +template<> inline FGMCOuterApplicationWrapper FGMCOuterApplicationWrapper::Make(TSubclassOf Effect, FGMCAbilityEffectData InitializationData) +{ + FGMCOuterApplicationWrapper Wrapper; + Wrapper.Type = EGMC_AddEffect; + Wrapper.OuterApplicationData = FInstancedStruct::Make(); + FGMCOuterEffectAdd& Data = Wrapper.OuterApplicationData.GetMutable(); + Data.EffectClass = Effect; + Data.InitializationData = InitializationData; + return Wrapper; +} + +template<> inline FGMCOuterApplicationWrapper FGMCOuterApplicationWrapper::Make(TArray Ids) +{ + FGMCOuterApplicationWrapper Wrapper; + Wrapper.Type = EGMC_RemoveEffect; + Wrapper.OuterApplicationData = FInstancedStruct::Make(); + FGMCOuterEffectRemove& Data = Wrapper.OuterApplicationData.GetMutable(); + Data.Ids = Ids; + return Wrapper; +} diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index ce6f4455..d631ac30 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -12,7 +12,7 @@ class UGMC_AbilitySystemComponent; UENUM(BlueprintType) -enum class EEffectType : uint8 +enum class EGMASEffectType : uint8 { Instant, // Applies Instantly Duration, // Lasts for X time @@ -20,7 +20,7 @@ enum class EEffectType : uint8 }; UENUM(BlueprintType) -enum class EEffectState : uint8 +enum class EGMASEffectState : uint8 { Initialized, // Applies Instantly Started, // Lasts for X time @@ -60,12 +60,15 @@ struct FGMCAbilityEffectData UPROPERTY() int EffectID; - UPROPERTY() + UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") double StartTime; - UPROPERTY() + UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") double EndTime; + UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") + double CurrentDuration{0.f}; + // Instantly applies effect then exits. Will not tick. UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") bool bIsInstant = true; @@ -91,6 +94,13 @@ struct FGMCAbilityEffectData UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") bool bPeriodTickAtStart = false; + // Time in seconds that the client has to apply itself an external effect before the server will force it. If this time is reach, a rollback is likely to happen. + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", AdvancedDisplay) + float ClientGraceTime = 1.f; + + UPROPERTY() + int LateApplicationID = -1; + // Tag to identify this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTag EffectTag; @@ -108,11 +118,11 @@ struct FGMCAbilityEffectData // Tags that the owner must have to apply and maintain this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - FGameplayTagContainer OngoingMustHaveTags; + FGameplayTagContainer MustHaveTags; // Tags that the owner must not have to apply and maintain this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - FGameplayTagContainer OngoingMustNotHaveTags; + FGameplayTagContainer MustNotHaveTags; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedAbilities; @@ -121,6 +131,10 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer PausePeriodicEffect; + // On activation, will end ability present in this container + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + FGameplayTagContainer CancelAbilityOnActivation; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") TArray Modifiers; @@ -130,14 +144,14 @@ struct FGMCAbilityEffectData return StartTime == Other.StartTime && EndTime == Other.EndTime; }; - bool IsValid() + bool IsValid() const { return GrantedTags != FGameplayTagContainer() || GrantedAbilities != FGameplayTagContainer() || Modifiers.Num() > 0 - || OngoingMustHaveTags != FGameplayTagContainer() || OngoingMustNotHaveTags != FGameplayTagContainer(); + || MustHaveTags != FGameplayTagContainer() || MustNotHaveTags != FGameplayTagContainer(); } FString ToString() const{ - return FString::Printf(TEXT("[id: %d] [Tag: %s] (Duration: %f)"), EffectID, *EffectTag.ToString(), Duration); + return FString::Printf(TEXT("[id: %d] [Tag: %s] (Duration: %.3lf) (CurrentDuration: %.3lf)"), EffectID, *EffectTag.ToString(), Duration, CurrentDuration); } }; @@ -152,7 +166,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject GENERATED_BODY() public: - EEffectState CurrentState; + EGMASEffectState CurrentState; UPROPERTY(EditAnywhere, Category = "GMCAbilitySystem") FGMCAbilityEffectData EffectData; @@ -161,15 +175,34 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject virtual void InitializeEffect(FGMCAbilityEffectData InitializationData); virtual void EndEffect(); + + virtual void BeginDestroy() override; virtual void Tick(float DeltaTime); + // Return the current duration of the effect + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + float GetCurrentDuration() const { return EffectData.CurrentDuration; } + + // Return the current duration of the effect + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + FGMCAbilityEffectData GetEffectData() const { return EffectData; } + + // Return the current duration of the effect + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + float GetEffectTotalDuration() const { return EffectData.Duration; } + UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Effect Tick"), Category="GMCAbilitySystem") void TickEvent(float DeltaTime); + + // Dynamic Condition allow you to avoid applying the attribute modifier if a condition is not met, for example, a sprint effect with attribute cost when the player is below a certain speed. + // However, this is not stopping the effect. + UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Dynamic Condition"), Category="GMCAbilitySystem") + bool AttributeDynamicCondition() const; virtual void PeriodTick(); - void UpdateState(EEffectState State, bool Force=false); + void UpdateState(EGMASEffectState State, bool Force=false); virtual bool IsPeriodPaused(); @@ -188,10 +221,13 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject // Tags void AddTagsToOwner(); - void RemoveTagsFromOwner(); + + // bPreserveOnMultipleInstances: If true, will not remove tags if there are multiple instances of the same effect + void RemoveTagsFromOwner(bool bPreserveOnMultipleInstances = true); void AddAbilitiesToOwner(); void RemoveAbilitiesFromOwner(); + void EndActiveAbilitiesFromOwner(); // Does the owner have any of the tags from the container? bool DoesOwnerHaveTagFromContainer(FGameplayTagContainer& TagContainer) const; From 177814312005120e5cdaf0c51c3c070500573145 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Sun, 15 Sep 2024 15:37:13 -0700 Subject: [PATCH 09/63] Update SetTargetDataGameplayTag.cpp fix: Remove super call for SetTargetDataGameplayTag --- .../Private/Ability/Tasks/SetTargetDataGameplayTag.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp index a3a814c7..6847bc08 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp @@ -26,8 +26,6 @@ void UGMCAbilityTask_SetTargetDataGameplayTag::ProgressTask(FInstancedStruct& Ta } void UGMCAbilityTask_SetTargetDataGameplayTag::ClientProgressTask(){ - Super::ClientProgressTask(); - FGMCAbilityTaskTargetDataGameplayTag TaskData; TaskData.TaskType = EGMCAbilityTaskDataType::Progress; TaskData.AbilityID = Ability->GetAbilityID(); From 1a7dcce69b9aaf5e9a022a536d3d614430a0dea1 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Thu, 14 Nov 2024 23:28:35 -0800 Subject: [PATCH 10/63] Remove Extra MarkDirty call Same patch that went into main #96 --- .../Private/Components/GMCAbilityComponent.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index d8c2f254..e8ca6cd2 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -672,8 +672,7 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() Attribute.CalculateValue(); UnBoundAttributes.MarkItemDirty(Attribute); } - UnBoundAttributes.MarkArrayDirty(); - + for (const FAttribute& Attribute : OldUnBoundAttributes.Items) { Attribute.CalculateValue(); From ffac92be9b9f29b24d47626df9a5bfdd72fe898c Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Fri, 15 Nov 2024 02:56:30 -0800 Subject: [PATCH 11/63] Fix unbound attribute replication issues --- .../Components/GMCAbilityComponent.cpp | 40 +++++++++---------- .../Public/Attributes/GMCAttributes.h | 3 +- .../Public/Components/GMCAbilityComponent.h | 2 + 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index e8ca6cd2..35aa879a 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -616,6 +616,7 @@ void UGMC_AbilitySystemComponent::PreLocalMoveExecution() void UGMC_AbilitySystemComponent::BeginPlay() { Super::BeginPlay(); + InitializeStartingAbilities(); InitializeAbilityMap(); SetStartingTags(); @@ -625,6 +626,7 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() { BoundAttributes = FGMCAttributeSet(); UnBoundAttributes = FGMCUnboundAttributeSet(); + OldUnBoundAttributes = FGMCUnboundAttributeSet(); if(AttributeDataAssets.IsEmpty()) return; // Loop through each of the data assets inputted into the component to create new attributes. @@ -645,15 +647,9 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() if(AttributeData.bGMCBound){ BoundAttributes.AddAttribute(NewAttribute); } - else if (GetOwnerRole() == ROLE_Authority || GetNetMode() == NM_Standalone) { - // FFastArraySerializer will duplicate all attributes on first replication if we - // add the attributes on the clients as well. - UnBoundAttributes.AddAttribute(NewAttribute); - - } - - if (!AttributeData.bGMCBound) { + else { // Initiate old unbound attributes + UnBoundAttributes.AddAttribute(NewAttribute); OldUnBoundAttributes.AddAttribute(NewAttribute); } } @@ -677,7 +673,7 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() { Attribute.CalculateValue(); } - + OldBoundAttributes = BoundAttributes; } @@ -1165,32 +1161,32 @@ void UGMC_AbilitySystemComponent::OnRep_UnBoundAttributes() UE_LOG(LogGMCAbilitySystem, Error, TEXT("OnRep_UnBoundAttributes: Mismatched Attribute Old != New Value !")); } + CheckUnBoundAttributeChanges(); + +} + +void UGMC_AbilitySystemComponent::CheckUnBoundAttributeChanges() +{ TArray& OldAttributes = OldUnBoundAttributes.Items; const TArray& CurrentAttributes = UnBoundAttributes.Items; TMap OldValues; // If this mitchmatch, that mean we need to reset the number of attributes - for (FAttribute& Attribute : OldAttributes){ OldValues.Add(Attribute.Tag, &Attribute.Value); } - - for (const FAttribute& Attribute : CurrentAttributes){ if (OldValues.Contains(Attribute.Tag) && *OldValues[Attribute.Tag] != Attribute.Value){ NativeAttributeChangeDelegate.Broadcast(Attribute.Tag, *OldValues[Attribute.Tag], Attribute.Value); OnAttributeChanged.Broadcast(Attribute.Tag, *OldValues[Attribute.Tag], Attribute.Value); - UnBoundAttributes.MarkAttributeDirty(Attribute); // Update Old Value *OldValues[Attribute.Tag] = Attribute.Value; } } - - } //BP Version @@ -1435,6 +1431,7 @@ bool UGMC_AbilitySystemComponent::SetAttributeValueByTag(FGameplayTag AttributeT } Att->CalculateValue(); + UnBoundAttributes.MarkAttributeDirty(*Att); return true; } return false; @@ -1503,8 +1500,9 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi if (const FAttribute* AffectedAttribute = GetAttributeByTag(AttributeModifier.AttributeTag)) { - // If we are unbound that means we shouldn't predict. + // If attribute is unbound and this is the client that means we shouldn't predict. if(!AffectedAttribute->bIsGMCBound && !HasAuthority()) return; + float OldValue = AffectedAttribute->Value; FGMCUnboundAttributeSet OldUnboundAttributes = UnBoundAttributes; @@ -1519,16 +1517,14 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi { OnAttributeChanged.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); NativeAttributeChangeDelegate.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); - } - BoundAttributes.MarkAttributeDirty(*AffectedAttribute); - UnBoundAttributes.MarkAttributeDirty(*AffectedAttribute); - if (!AffectedAttribute->bIsGMCBound) { - OnRep_UnBoundAttributes(); + if (!AffectedAttribute->bIsGMCBound) + { + UnBoundAttributes.MarkAttributeDirty(*AffectedAttribute); + } } } } - // ReplicatedProps void UGMC_AbilitySystemComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const { diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h index bf79f365..a5a62cad 100644 --- a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h @@ -155,8 +155,7 @@ struct FGMCUnboundAttributeSet : public FFastArraySerializer void AddAttribute(const FAttribute& NewAttribute) { - Items.Add(NewAttribute); - MarkArrayDirty(); + MarkItemDirty(Items.Add_GetRef(NewAttribute)); } TArray GetAttributes() const diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index cad75c50..42ec16ea 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -219,6 +219,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION() void OnRep_UnBoundAttributes(); + void CheckUnBoundAttributeChanges(); + /** * Applies an effect to the Ability Component * From 1bd4eb9c641bc54b91993c77b866e4d7b0bb7ce1 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 20 Nov 2024 18:21:15 -0800 Subject: [PATCH 12/63] Rooibot queue refactor (#95) (#97) * Beginnings of general queue implementation * Further work on bound queues. * Convert abilities over to using the new bound operations queue. * Refactor to generalize ability queue processing * Convert effects to bound queue * Refactor and cleanup * A little more refactoring * Support server-auth effects queued via GMC moves. Note that this requires adding an SV_PreRemoteMoveExecution to your movement component, and calling into GMAS's PreRemoteMove call in that. * Ensure that Queue-via-GMC effects work in standalone as well. * Queue rework and cleanup finished! * Last few tweaks to the queue setup. * Further queue refactor and cleanup. New queue types (ClientAuth, PredictedQueued) added. * Add effect handles, so that PredictedQueued works right. * Small cleanup on effect handle expiry. * Correct desync for server-auth queue. * Ensure we don't use the same ID if queuing two server-auth things in the same frame. * Fix for Reznok's two-effects-in-one-frame issue. * Correct issue with 2+ PredictedQueued operations in one tick, or 3+ ServerAuth queued. * Fix off-by-one on server auth effect tick * Fix a task ticking bug when multiple tasks happened in a row * Fix:Add SourceComponent references to effect applications * Add a bool to EffectData to preserve multiple instances of granted tags --------- Co-authored-by: Rachel Blackman Co-authored-by: Rachel Blackman --- .../Private/Ability/GMCAbility.cpp | 14 +- .../Ability/Tasks/WaitForInputKeyPress.cpp | 11 +- .../Ability/Tasks/WaitForInputKeyRelease.cpp | 2 +- .../Components/GMCAbilityComponent.cpp | 853 +++++++++++++----- .../Private/Effects/GMCAbilityEffect.cpp | 28 +- .../Private/Utility/GMASBoundQueue.cpp | 1 + .../Public/Components/GMCAbilityComponent.h | 171 +++- .../Components/GMCAbilityOuterApplication.h | 86 -- .../Public/Effects/GMCAbilityEffect.h | 5 + .../Public/Utility/GMASBoundQueue.h | 582 ++++++++++++ 10 files changed, 1410 insertions(+), 343 deletions(-) create mode 100644 Source/GMCAbilitySystem/Private/Utility/GMASBoundQueue.cpp delete mode 100644 Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h create mode 100644 Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index f1ebb91e..c4fa30a7 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -59,18 +59,20 @@ void UGMCAbility::AncillaryTick(float DeltaTime){ void UGMCAbility::TickTasks(float DeltaTime) { - for (const TPair& Task : RunningTasks) + for (int i=0; i < RunningTasks.Num(); i++) { - if (Task.Value == nullptr) {continue;} - Task.Value->Tick(DeltaTime); + UGMCAbilityTaskBase* Task = RunningTasks[i]; + if (Task == nullptr) {continue;} + Task->Tick(DeltaTime); } } void UGMCAbility::AncillaryTickTasks(float DeltaTime){ - for (const TPair& Task : RunningTasks) + for (int i=0; i < RunningTasks.Num(); i++) { - if (Task.Value == nullptr) {continue;} - Task.Value->AncillaryTick(DeltaTime); + UGMCAbilityTaskBase* Task = RunningTasks[i]; + if (Task == nullptr) {continue;} + Task->AncillaryTick(DeltaTime); } } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp index 0492f27d..2e18f150 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp @@ -24,11 +24,14 @@ void UGMCAbilityTask_WaitForInputKeyPress::Activate() { UEnhancedInputComponent* const InputComponent = GetEnhancedInputComponent(); - const FEnhancedInputActionEventBinding& Binding = InputComponent->BindAction( - Ability->AbilityInputAction, ETriggerEvent::Started, this, - &UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed); + if (InputComponent) + { + const FEnhancedInputActionEventBinding& Binding = InputComponent->BindAction( + Ability->AbilityInputAction, ETriggerEvent::Started, this, + &UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed); - InputBindingHandle = Binding.GetHandle(); + InputBindingHandle = Binding.GetHandle(); + } } else { diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index d2184dc7..77d4396e 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -19,7 +19,7 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Activate() UEnhancedInputComponent* const InputComponent = GetEnhancedInputComponent(); - if (Ability->AbilityInputAction != nullptr) + if (Ability->AbilityInputAction != nullptr && InputComponent != nullptr) { FEnhancedInputActionEventBinding& Binding = InputComponent->BindAction( Ability->AbilityInputAction, ETriggerEvent::Completed, this, diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 35aa879a..e36a892f 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -142,20 +142,6 @@ void UGMC_AbilitySystemComponent::BindReplicationData() EGMC_CombineMode::CombineIfUnchanged, EGMC_SimulationMode::Periodic_Output, EGMC_InterpolationFunction::TargetValue); - - // AbilityData Binds - // These are mostly client-inputs made to the server as Ability Requests - GMCMovementComponent->BindInt(AbilityData.AbilityActivationID, - EGMC_PredictionMode::ClientAuth_Input, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::None, - EGMC_InterpolationFunction::TargetValue); - - GMCMovementComponent->BindGameplayTag(AbilityData.InputTag, - EGMC_PredictionMode::ClientAuth_Input, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::None, - EGMC_InterpolationFunction::TargetValue); // TaskData Bind GMCMovementComponent->BindInstancedStruct(TaskData, @@ -170,16 +156,14 @@ void UGMC_AbilitySystemComponent::BindReplicationData() EGMC_SimulationMode::PeriodicAndOnChange_Output, EGMC_InterpolationFunction::TargetValue); - GMCMovementComponent->BindInstancedStruct(AcknowledgeId, - EGMC_PredictionMode::ClientAuth_Input, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::None, - EGMC_InterpolationFunction::TargetValue); + // Bind our operation queues. + QueuedAbilityOperations.BindToGMC(GMCMovementComponent); + QueuedEffectOperations.BindToGMC(GMCMovementComponent); + QueuedEffectOperations_ClientAuth.BindToGMC(GMCMovementComponent); } void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsCombinedClientMove) { - OnAncillaryTick.Broadcast(DeltaTime); ClientHandlePendingEffect(); @@ -192,25 +176,26 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb TickActiveCooldowns(DeltaTime); TickAncillaryActiveAbilities(DeltaTime); - - // Activate abilities from ancillary tick if they have bActivateOnMovementTick set to false - if (AbilityData.InputTag != FGameplayTag::EmptyTag) + + // Check if we have a valid operation + TGMASBoundQueueOperation Operation; + if (QueuedAbilityOperations.GetCurrentBoundOperation(Operation, true)) { - TryActivateAbilitiesByInputTag(AbilityData.InputTag, AbilityData.ActionInput, false); + ProcessAbilityOperation(Operation, false); } SendTaskDataToActiveAbility(false); ClearAbilityAndTaskData(); + QueuedEffectOperations_ClientAuth.ClearCurrentOperation(); bInGMCTime = false; } -TArray UGMC_AbilitySystemComponent::GetActivesEffectByTag(FGameplayTag GameplayTag) const { +TArray UGMC_AbilitySystemComponent::GetActiveEffectsByTag(FGameplayTag GameplayTag) const +{ TArray ActiveEffectsFound; - UE_LOG(LogGMCAbilitySystem, Error, TEXT("Searching for Active Effects with Tag: %s"), *GameplayTag.ToString()); - for (const TTuple& EffectFound : ActiveEffects) { if (IsValid(EffectFound.Value) && EffectFound.Value->EffectData.EffectTag.MatchesTag(GameplayTag)) { ActiveEffectsFound.Add(EffectFound.Value); @@ -221,7 +206,8 @@ TArray UGMC_AbilitySystemComponent::GetActivesEffectByTag(FG } -UGMCAbilityEffect* UGMC_AbilitySystemComponent::GetFirstActiveEffectByTag(FGameplayTag GameplayTag) const { +UGMCAbilityEffect* UGMC_AbilitySystemComponent::GetFirstActiveEffectByTag(FGameplayTag GameplayTag) const +{ for (auto& EffectFound : ActiveEffects) { if (EffectFound.Value && EffectFound.Value->EffectData.EffectTag.MatchesTag(GameplayTag)) { return EffectFound.Value; @@ -341,7 +327,6 @@ TArray UGMC_AbilitySystemComponent::GetActiveTagsByParentTag(const void UGMC_AbilitySystemComponent::TryActivateAbilitiesByInputTag(const FGameplayTag& InputTag, const UInputAction* InputAction, bool bFromMovementTick) { - for (const TSubclassOf& ActivatedAbility : GetGrantedAbilitiesByTag(InputTag)) { const UGMCAbility* AbilityCDO = ActivatedAbility->GetDefaultObject(); @@ -410,18 +395,14 @@ void UGMC_AbilitySystemComponent::QueueAbility(FGameplayTag InputTag, const UInp FGMCAbilityData Data; Data.InputTag = InputTag; Data.ActionInput = InputAction; - QueuedAbilities.Push(Data); + + TGMASBoundQueueOperation Operation; + QueuedAbilityOperations.QueueOperation(Operation, EGMASBoundQueueOperationType::Activate, InputTag, Data); } int32 UGMC_AbilitySystemComponent::GetQueuedAbilityCount(FGameplayTag AbilityTag) { - int32 Result = 0; - - for (const auto& QueuedData : QueuedAbilities) - { - if (QueuedData.InputTag == AbilityTag) Result++; - } - return Result; + return QueuedAbilityOperations.NumMatching(AbilityTag, EGMASBoundQueueOperationType::Activate); } int32 UGMC_AbilitySystemComponent::GetActiveAbilityCount(TSubclassOf AbilityClass) @@ -572,16 +553,21 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) // Abilities CleanupStaleAbilities(); + + // Advance our queue action timers. + QueuedAbilityOperations.GenPredictionTick(DeltaTime); + QueuedEffectOperations.GenPredictionTick(DeltaTime); // Was an ability used? - if (AbilityData.InputTag != FGameplayTag::EmptyTag) + if (TGMASBoundQueueOperation Operation; + QueuedAbilityOperations.GetCurrentBoundOperation(Operation, true)) { - TryActivateAbilitiesByInputTag(AbilityData.InputTag, AbilityData.ActionInput, true); + ProcessAbilityOperation(Operation, true); } - SendTaskDataToActiveAbility(true); - + ServerHandlePredictedPendingEffect(DeltaTime); + SendTaskDataToActiveAbility(true); } void UGMC_AbilitySystemComponent::GenSimulationTick(float DeltaTime) @@ -601,16 +587,26 @@ void UGMC_AbilitySystemComponent::GenSimulationTick(float DeltaTime) void UGMC_AbilitySystemComponent::PreLocalMoveExecution() { - if (QueuedAbilities.Num() > 0) - { - AbilityData = QueuedAbilities.Pop(); - } if (QueuedTaskData.Num() > 0) { TaskData = QueuedTaskData.Pop(); } - + // Advance our client-auth queues. + QueuedAbilityOperations.PreLocalMovement(); + QueuedEffectOperations_ClientAuth.PreLocalMovement(); + + if (GetNetMode() == NM_Standalone || GMCMovementComponent->IsLocallyControlledServerPawn()) + { + // We'll never get the pre remote movement trigger in this case, soooo... + QueuedEffectOperations.PreRemoteMovement(); + } +} + +void UGMC_AbilitySystemComponent::PreRemoteMoveExecution() +{ + // Advance our server-auth queues. + QueuedEffectOperations.PreRemoteMovement(); } void UGMC_AbilitySystemComponent::BeginPlay() @@ -780,7 +776,10 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime) } Effect.Value->Tick(DeltaTime); - if (Effect.Value->bCompleted) {CompletedActiveEffects.Push(Effect.Key);} + if (Effect.Value->bCompleted) + { + CompletedActiveEffects.Push(Effect.Key); + } // Check for predicted effects that have not been server confirmed if (!HasAuthority() && @@ -802,6 +801,19 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime) ActiveEffects.Remove(EffectID); ActiveEffectsData.RemoveAll([EffectID](const FGMCAbilityEffectData& EffectData) {return EffectData.EffectID == EffectID;}); } + + // Clean effect handles + TArray CurrentHandles; + EffectHandles.GetKeys(CurrentHandles); + for (const int Handle : CurrentHandles) + { + const auto& Data = EffectHandles[Handle]; + if (Data.NetworkId > 0 && !ActiveEffects.Contains(Data.NetworkId)) + { + EffectHandles.Remove(Handle); + } + } + } void UGMC_AbilitySystemComponent::TickActiveAbilities(float DeltaTime) @@ -868,119 +880,94 @@ void UGMC_AbilitySystemComponent::CheckRemovedEffects() } } -void UGMC_AbilitySystemComponent::AddPendingEffectApplications(FGMCOuterApplicationWrapper& Wrapper) { - check(HasAuthority()) - - Wrapper.ClientGraceTimeRemaining = 1.f; - Wrapper.LateApplicationID = GenerateLateApplicationID(); - - PendingApplicationServer.Add(Wrapper); - RPCClientAddPendingEffectApplication(Wrapper); -} - - -void UGMC_AbilitySystemComponent::RPCClientAddPendingEffectApplication_Implementation( - FGMCOuterApplicationWrapper Wrapper) { - PendingApplicationClient.Add(Wrapper); -} - - - void UGMC_AbilitySystemComponent::ServerHandlePendingEffect(float DeltaTime) { if (!HasAuthority()) { return; } - FGMCAcknowledgeId& AckId = AcknowledgeId.GetMutable(); - - - for (int i = PendingApplicationServer.Num() - 1; i >= 0; i--) { - FGMCOuterApplicationWrapper& Wrapper = PendingApplicationServer[i]; + // Handle our GMC-replicated effect operation, if any. We can't actually replicate + // the message server-to-client via GMC, but we *can* preserve it in the move history in + // case it is relevant to replay. + TGMASBoundQueueOperation BoundOperation; + if (QueuedEffectOperations.GetCurrentBoundOperation(BoundOperation, true)) + { + // Move this into our RPC queue to wait on acknowledgment. + QueuedEffectOperations.QueuePreparedOperation(BoundOperation, false); - if (Wrapper.ClientGraceTimeRemaining <= 0.f || AckId.Id.Contains(Wrapper.LateApplicationID)) { + // And send it via RPC, so that the client gets it. + ClientQueueEffectOperation(BoundOperation); + }; - switch (Wrapper.Type) { - case EGMC_AddEffect: { - const FGMCOuterEffectAdd& Data = Wrapper.OuterApplicationData.Get(); - UGMCAbilityEffect* AbilityEffect = DuplicateObject(Data.EffectClass->GetDefaultObject(), this); - AbilityEffect->EffectData.EffectID = Wrapper.LateApplicationID; - FGMCAbilityEffectData EffectData = Data.InitializationData.IsValid() ? Data.InitializationData : AbilityEffect->EffectData; - UGMCAbilityEffect* FX = ApplyAbilityEffect(AbilityEffect, EffectData); - if (Wrapper.ClientGraceTimeRemaining <= 0.f) { - UE_LOG(LogGMCAbilitySystem, Log, TEXT("Client Add Effect `%s ` Missed Grace time, Force application : id: %d"), *GetNameSafe(Data.EffectClass), FX->EffectData.EffectID); - } - } break; - case EGMC_RemoveEffect: { - const FGMCOuterEffectRemove& Data = Wrapper.OuterApplicationData.Get(); - RemoveEffectById(Data.Ids); - if (Wrapper.ClientGraceTimeRemaining <= 0.f) { - UE_LOG(LogGMCAbilitySystem, Log, TEXT("Client Remove Effect Missed Grace time, Force remove")); - } - } break; + // Handle our 'outer' RPC effect operations. + QueuedEffectOperations.DeductGracePeriod(DeltaTime); + auto Operations = QueuedEffectOperations.GetQueuedRPCOperations(); + for (auto& Operation : Operations) { + if (ShouldProcessEffectOperation(Operation, true)) + { + if (Operation.GracePeriodExpired()) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Client effect operation missed grace period, forcing on server.")) } - PendingApplicationServer.RemoveAt(i); - } - else { - Wrapper.ClientGraceTimeRemaining -= DeltaTime; + ProcessEffectOperation(Operation); + QueuedEffectOperations.RemoveOperationById(Operation.GetOperationId()); } - - } - } - -void UGMC_AbilitySystemComponent::ClientHandlePendingEffect() { - - - for (int i = PendingApplicationClient.Num() - 1; i >= 0; i--) +void UGMC_AbilitySystemComponent::ServerHandlePredictedPendingEffect(float DeltaTime) +{ + if (!HasAuthority()) return; + + // Check for any client-auth effects. + TGMASBoundQueueOperation BoundOperation; + if (QueuedEffectOperations_ClientAuth.GetCurrentBoundOperation(BoundOperation, true)) { - FGMCOuterApplicationWrapper& LateApplicationData = PendingApplicationClient[i]; - - switch (LateApplicationData.Type) { - case EGMC_AddEffect: { - const FGMCOuterEffectAdd& Data = LateApplicationData.OuterApplicationData.Get(); - - if (Data.EffectClass == nullptr) { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("ClientHandlePendingEffect: EffectClass is null")); - break; - } - - UGMCAbilityEffect* CDO = Data.EffectClass->GetDefaultObject(); + ProcessEffectOperation(BoundOperation); + } - if (CDO == nullptr) { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("ClientHandlePendingEffect: CDO is null")); - break; - } - - UGMCAbilityEffect* AbilityEffect = DuplicateObject(CDO, this); - AbilityEffect->EffectData.EffectID = LateApplicationData.LateApplicationID; - FGMCAbilityEffectData EffectData = Data.InitializationData.IsValid() ? Data.InitializationData : AbilityEffect->EffectData; - ApplyAbilityEffect(AbilityEffect, EffectData); - } break; - case EGMC_RemoveEffect: { - const FGMCOuterEffectRemove& Data = LateApplicationData.OuterApplicationData.Get(); - RemoveEffectById(Data.Ids); - } break; - } - - PendingApplicationClient.RemoveAt(i); - AcknowledgeId.GetMutable().Id.Add(LateApplicationData.LateApplicationID); - } + // Check for any queued-for-move predicted effects. + // We use the client auth effect queue's (otherwise-unused) RPC operations queue to avoid creating an entire new one. + while (QueuedEffectOperations_ClientAuth.PopNextRPCOperation(BoundOperation)) + { + ProcessEffectOperation(BoundOperation); + } + } -int UGMC_AbilitySystemComponent::GenerateLateApplicationID() { - int NewEffectID = static_cast(ActionTimer * 100); - while (ActiveEffects.Contains(NewEffectID)) +void UGMC_AbilitySystemComponent::ClientHandlePendingEffect() { + + // Handle our RPC effect operations. MoveCycle operations will be sent via RPC + // just like the Outer ones, but will be preserved in the movement history. + auto RPCOperations = QueuedEffectOperations.GetQueuedRPCOperations(); + for (auto& Operation : RPCOperations) { + if (QueuedEffectOperations.IsAcknowledged(Operation.GetOperationId())) + { + ProcessEffectOperation(Operation); + QueuedEffectOperations.RemoveOperationById(Operation.GetOperationId()); + } + if (ShouldProcessEffectOperation(Operation, false)) + { + QueuedEffectOperations.Acknowledge(Operation.GetOperationId()); + } + } +} + +void UGMC_AbilitySystemComponent::ClientHandlePredictedPendingEffect() +{ + TGMASBoundQueueOperation BoundOperation; + if (QueuedEffectOperations_ClientAuth.GetCurrentBoundOperation(BoundOperation)) + { + ProcessEffectOperation(BoundOperation); + } + + // We use the client auth effect queue's (otherwise-unused) RPC operations queue to avoid creating an entire new one. + while (QueuedEffectOperations_ClientAuth.PopNextRPCOperation(BoundOperation)) { - NewEffectID++; + ProcessEffectOperation(BoundOperation); } - - return NewEffectID; } - void UGMC_AbilitySystemComponent::RPCTaskHeartbeat_Implementation(int AbilityID, int TaskID) { if (ActiveAbilities.Contains(AbilityID) && ActiveAbilities[AbilityID] != nullptr) @@ -1054,6 +1041,7 @@ TArray> UGMC_AbilitySystemComponent::GetGrantedAbilitie void UGMC_AbilitySystemComponent::ClearAbilityAndTaskData() { AbilityData = FGMCAbilityData{}; + QueuedAbilityOperations.ClearCurrentOperation(); TaskData = FInstancedStruct::Make(FGMCAbilityTaskData{}); } @@ -1153,6 +1141,132 @@ void UGMC_AbilitySystemComponent::InitializeStartingAbilities() } } +bool UGMC_AbilitySystemComponent::ProcessAbilityOperation( + const TGMASBoundQueueOperation& Operation, bool bFromMovementTick) +{ + EGMASBoundQueueOperationType OperationType = Operation.GetOperationType(); + if (OperationType == EGMASBoundQueueOperationType::Activate) + { + TryActivateAbilitiesByInputTag(Operation.GetTag(), Operation.Payload.ActionInput, bFromMovementTick); + return true; + } + + if (OperationType == EGMASBoundQueueOperationType::Cancel) + { + EndAbilitiesByTag(Operation.GetTag()); + if (Operation.ItemClass) + { + EndAbilitiesByClass(Operation.ItemClass); + } + return true; + } + + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Received ability operation with invalid operation type %s for %s!"), + *UEnum::GetValueAsString(OperationType), *Operation.GetTag().ToString()) + return false; +} + +UGMCAbilityEffect* UGMC_AbilitySystemComponent::ProcessEffectOperation( + const TGMASBoundQueueOperation& Operation) +{ + EGMASBoundQueueOperationType OperationType = Operation.GetOperationType(); + + if (OperationType == EGMASBoundQueueOperationType::Add) + { + if (Operation.ItemClass == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted to process an add effect operation with no set class!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) + } + + UGMCAbilityEffect* Effect = DuplicateObject(Operation.ItemClass->GetDefaultObject(), this); + FGMCAbilityEffectData EffectData = Operation.Payload; + + if (!EffectData.IsValid()) + { + EffectData = Effect->EffectData; + } + + if (Operation.Header.PayloadIds.Ids.Num() > 0) + { + EffectData.EffectID = Operation.Header.PayloadIds.Ids[0]; + } + + if (EffectData.EffectID > 0 && ActiveEffects.Contains(EffectData.EffectID)) + { + const auto& ExistingEffect = ActiveEffects[EffectData.EffectID]; + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("[%20s] %s attempted to process an explicit ID add effect operation for %s with existing effect %d [%s]"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *Effect->GetClass()->GetName(), EffectData.EffectID, *ExistingEffect->GetClass()->GetName()) + return nullptr; + } + + ApplyAbilityEffect(Effect, EffectData); + + for (auto& [EffectHandle, EffectHandleData] : EffectHandles) + { + // If we don't already have a known effect ID, attach it to our handle now. + if (EffectHandleData.NetworkId <= 0 && EffectHandleData.OperationId == Operation.Header.OperationId) + { + EffectHandleData.NetworkId = Effect->EffectData.EffectID; + } + } + + return Effect; + } + + if (OperationType == EGMASBoundQueueOperationType::Remove) + { + for (auto& [Id, Effect]: ActiveEffects) + { + if (Operation.GetPayloadIds().Contains(Id)) + { + RemoveActiveAbilityEffect(Effect); + } + } + return nullptr; + } + + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Received ability operation with invalid operation type %s for %s!"), + *UEnum::GetValueAsString(OperationType), *Operation.ItemClass->GetName()) + return nullptr; +} + +bool UGMC_AbilitySystemComponent::ShouldProcessEffectOperation( + const TGMASBoundQueueOperation& Operation, bool bIsServer) const +{ + if (!Operation.IsValid()) + { + if (Operation.Header.OperationId != -1) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%s] %s received invalid queued effect operation %d!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Operation.Header.OperationId) + return false; + } + return false; + } + + if (bIsServer) + { + return HasAuthority() && (QueuedEffectOperations.IsAcknowledged(Operation.GetOperationId()) || + Operation.GracePeriodExpired() || GetNetMode() == NM_Standalone); + } + else + { + return !QueuedEffectOperations.IsAcknowledged(Operation.GetOperationId()) && (GMCMovementComponent->IsLocallyControlledServerPawn() || GMCMovementComponent->IsAutonomousProxy()); + } +} + +void UGMC_AbilitySystemComponent::ClientQueueEffectOperation( + const TGMASBoundQueueOperation& Operation) +{ + RPCClientQueueEffectOperation(Operation.Header); +} + +void UGMC_AbilitySystemComponent::RPCClientQueueEffectOperation_Implementation(const FGMASBoundQueueRPCHeader& Header) +{ + QueuedEffectOperations.QueueOperationFromHeader(Header, false); +} + void UGMC_AbilitySystemComponent::OnRep_UnBoundAttributes() { @@ -1189,6 +1303,84 @@ void UGMC_AbilitySystemComponent::CheckUnBoundAttributeChanges() } } +int UGMC_AbilitySystemComponent::GetNextAvailableEffectID() const +{ + if (ActionTimer == 0) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[ApplyAbilityEffect] Action Timer is 0, cannot generate Effect ID. Is it a listen server smoothed pawn?")); + return -1; + } + + int NewEffectID = static_cast(ActionTimer * 100); + while (ActiveEffects.Contains(NewEffectID) || CheckIfEffectIDQueued(NewEffectID)) + { + NewEffectID++; + } + UE_LOG(LogGMCAbilitySystem, VeryVerbose, TEXT("[Server: %hhd] Generated Effect ID: %d"), HasAuthority(), NewEffectID); + + return NewEffectID; +} + +bool UGMC_AbilitySystemComponent::CheckIfEffectIDQueued(int EffectID) const +{ + for (const auto& Operation : QueuedEffectOperations.GetQueuedRPCOperations()) + { + if (Operation.Payload.EffectID == EffectID) + { + return true; + } + } + + for (const auto& Operation : QueuedEffectOperations_ClientAuth.GetQueuedRPCOperations()) + { + if (Operation.Payload.EffectID == EffectID) + { + return true; + } + } + + return false; +} + +int UGMC_AbilitySystemComponent::CreateEffectOperation( + TGMASBoundQueueOperation& OutOperation, + const TSubclassOf& EffectClass, + const FGMCAbilityEffectData& EffectData, + bool bForcedEffectId, + EGMCAbilityEffectQueueType QueueType) +{ + TArray PayloadIds {}; + + FGMCAbilityEffectData PayloadData; + if (EffectData.IsValid()) + { + PayloadData = EffectData; + } + else + { + PayloadData = EffectClass->GetDefaultObject()->EffectData; + } + + if (bForcedEffectId) + { + if (PayloadData.EffectID == 0) + { + PayloadData.EffectID = GetNextAvailableEffectID(); + } + PayloadIds.Add(PayloadData.EffectID); + } + + if (QueueType == EGMCAbilityEffectQueueType::PredictedQueued || QueueType == EGMCAbilityEffectQueueType::ClientAuth) + { + QueuedEffectOperations_ClientAuth.MakeOperation(OutOperation, EGMASBoundQueueOperationType::Add, PayloadData.EffectTag, PayloadData, PayloadIds, EffectClass, 1.f, static_cast(QueueType)); + } + else + { + QueuedEffectOperations.MakeOperation(OutOperation, EGMASBoundQueueOperationType::Add, PayloadData.EffectTag, PayloadData, PayloadIds, EffectClass, 1.f, static_cast(QueueType)); + } + return PayloadData.EffectID; +} + //BP Version UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData, bool bOuterActivation) { @@ -1198,32 +1390,208 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Operation; + if (CreateEffectOperation(Operation, Effect, InitializationData, bOuterActivation, bOuterActivation ? EGMCAbilityEffectQueueType::ServerAuth : EGMCAbilityEffectQueueType::Predicted) == -1) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s could not create an effect of type %s!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *Effect->GetName()) + return nullptr; + } + // We are trying to apply an effect from an outside source, so we will need to go trough a different routing to apply it if (bOuterActivation) { if (HasAuthority()) { - FGMCOuterApplicationWrapper Wrapper = FGMCOuterApplicationWrapper::Make(Effect, InitializationData); - AddPendingEffectApplications(Wrapper); + QueuedEffectOperations.QueuePreparedOperation(Operation, false); + ClientQueueEffectOperation(Operation); } return nullptr; } - + if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone) + { + // For backwards compatibility, we do not reject this if we're outside a movement cycle. However, we will at least + // log it. + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("[%20s] %s tried to apply a predicted effect of type %s outside a movement cycle!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *Effect->GetName()) + } + return ProcessEffectOperation(Operation); +} + +int32 UGMC_AbilitySystemComponent::GetNextAvailableEffectHandle() const +{ + if (ActionTimer == 0) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[ApplyAbilityEffect] Action Timer is 0, cannot generate Effect ID. Is it a listen server smoothed pawn?")); + return -1; + } + + int NewEffectHandle = static_cast(ActionTimer * 100); + while (EffectHandles.Contains(NewEffectHandle)) + { + NewEffectHandle++; + } - UGMCAbilityEffect* AbilityEffect = DuplicateObject(Effect->GetDefaultObject(), this); - - FGMCAbilityEffectData EffectData; - if (InitializationData.IsValid()) + return NewEffectHandle; +} + +void UGMC_AbilitySystemComponent::GetEffectFromHandle_BP(int EffectHandle, bool& bOutSuccess, int32& OutEffectNetworkId, + UGMCAbilityEffect*& OutEffect) +{ + bOutSuccess = GetEffectFromHandle(EffectHandle, OutEffectNetworkId, OutEffect); +} + +bool UGMC_AbilitySystemComponent::GetEffectFromHandle(int EffectHandle, int32& OutEffectNetworkId, + UGMCAbilityEffect*& OutEffect) const +{ + FGMASQueueOperationHandle HandleData; + + if (!GetEffectHandle(EffectHandle, HandleData)) return false; + + OutEffectNetworkId = HandleData.NetworkId; + if (HandleData.NetworkId > 0) { - EffectData = InitializationData; + OutEffect = ActiveEffects[HandleData.NetworkId]; } - else + return true; +} + +bool UGMC_AbilitySystemComponent::GetEffectHandle(int EffectHandle, FGMASQueueOperationHandle& HandleData) const +{ + for (auto& [ID, Handle] : EffectHandles) { - EffectData = AbilityEffect->EffectData; + if (Handle.Handle == EffectHandle) + { + HandleData = Handle; + return true; + } + } + return false; +} + +void UGMC_AbilitySystemComponent::RemoveEffectHandle(int EffectHandle) +{ + EffectHandles.Remove(EffectHandle); +} + +void UGMC_AbilitySystemComponent::ApplyAbilityEffectSafe(TSubclassOf EffectClass, + FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, bool& OutSuccess, int& OutEffectHandle, int& OutEffectId, + UGMCAbilityEffect*& OutEffect) +{ + OutSuccess = ApplyAbilityEffect(EffectClass, InitializationData, QueueType, OutEffectHandle, OutEffectId, OutEffect); +} + +bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf EffectClass, + FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectHandle, int& OutEffectId, UGMCAbilityEffect*& OutEffect) +{ + OutEffect = nullptr; + OutEffectId = -1; + OutEffectHandle = -1; + if (EffectClass == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Trying to apply Effect, but effect is null!")); + return false; + } + + const bool bPregenerateEffectId = QueueType != EGMCAbilityEffectQueueType::Predicted && QueueType != EGMCAbilityEffectQueueType::PredictedQueued; + + TGMASBoundQueueOperation Operation; + const int EffectID = CreateEffectOperation(Operation, EffectClass, InitializationData, bPregenerateEffectId, QueueType); + if (bPregenerateEffectId && EffectID == -1) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s could not create an effect of type %s!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *EffectClass->GetName()) + return false; } + + FGMASQueueOperationHandle HandleData; + HandleData.Handle = GetNextAvailableEffectHandle(); + HandleData.NetworkId = EffectID; + HandleData.OperationId = Operation.Header.OperationId; + EffectHandles.Add(HandleData.Handle, HandleData); - ApplyAbilityEffect(AbilityEffect, EffectData); - return AbilityEffect; + switch(QueueType) + { + case EGMCAbilityEffectQueueType::Predicted: + { + if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted to apply predicted effect %d of type %s outside of a GMC move!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), EffectID, *EffectClass->GetName()) + return false; + } + + // Apply effect immediately. + OutEffect = ProcessEffectOperation(Operation); + OutEffectId = OutEffect->EffectData.EffectID; + OutEffectHandle = HandleData.Handle; + return true; + } + case EGMCAbilityEffectQueueType::PredictedQueued: + { + if (GMCMovementComponent->IsExecutingMove()) + { + // We're in a move context, just add it directly rather than queuing. + OutEffect = ProcessEffectOperation(Operation); + OutEffectId = OutEffect->EffectData.EffectID; + } + else + { + // We utilize the ClientAuth queue's RPC queue for the sake of convenience. + QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, false); + } + OutEffectHandle = HandleData.Handle; + return true; + } + case EGMCAbilityEffectQueueType::ServerAuthMove: + case EGMCAbilityEffectQueueType::ServerAuth: + { + if (!HasAuthority()) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted to apply server-queued effect %d of type %s on a client!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), EffectID, *EffectClass->GetName()) + return false; + } + + if (QueueType == EGMCAbilityEffectQueueType::ServerAuthMove) Operation.Header.RPCGracePeriodSeconds = 5.f; + + QueuedEffectOperations.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ServerAuthMove); + + if (QueueType == EGMCAbilityEffectQueueType::ServerAuth) + { + // Queue for RPC and throw this to our client. + ClientQueueEffectOperation(Operation); + } + + OutEffectId = EffectID; + OutEffectHandle = HandleData.Handle; + return true; + } + + case EGMCAbilityEffectQueueType::ClientAuth: + { + if (GetNetMode() != NM_Standalone && !GMCMovementComponent->IsAutonomousProxy() && !GMCMovementComponent->IsLocallyControlledServerPawn()) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted to apply client-auth effect %d of type %s on a server!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), EffectID, *EffectClass->GetName()) + return false; + } + + QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, true); + OutEffectId = EffectID; + return true; + } + } + + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted to apply effect of type %s but something has gone BADLY wrong!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *EffectClass->GetName()) + return false; +} + +UGMCAbilityEffect* UGMC_AbilitySystemComponent::GetEffectById(const int EffectId) const +{ + if (!ActiveEffects.Contains(EffectId)) return nullptr; + + return ActiveEffects[EffectId]; } UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(UGMCAbilityEffect* Effect, FGMCAbilityEffectData InitializationData) @@ -1241,19 +1609,7 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(UGMCAbilityEf if (Effect->EffectData.EffectID == 0) { - if (ActionTimer == 0) - { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[ApplyAbilityEffect] Action Timer is 0, cannot generate Effect ID. Is it a listen server smoothed pawn?")); - return nullptr; - } - - int NewEffectID = static_cast(ActionTimer * 100); - while (ActiveEffects.Contains(NewEffectID)) - { - NewEffectID++; - } - Effect->EffectData.EffectID = NewEffectID; - UE_LOG(LogGMCAbilitySystem, VeryVerbose, TEXT("[Server: %hhd] Generated Effect ID: %d"), HasAuthority(), Effect->EffectData.EffectID); + Effect->EffectData.EffectID = GetNextAvailableEffectID(); } // This is Replicated, so only server needs to manage it @@ -1265,8 +1621,9 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(UGMCAbilityEf { ProcessedEffectIDs.Add(Effect->EffectData.EffectID, false); } - + ActiveEffects.Add(Effect->EffectData.EffectID, Effect); + return Effect; } @@ -1278,17 +1635,26 @@ void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffect(UGMCAbilityEffect* E } if (!ActiveEffects.Contains(Effect->EffectData.EffectID)) return; + Effect->EndEffect(); } -int32 UGMC_AbilitySystemComponent::RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove, bool bOuterActivation) { +void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffectSafe(UGMCAbilityEffect* Effect, + EGMCAbilityEffectQueueType QueueType) +{ + if (Effect == nullptr) return; - if (NumToRemove < -1 || !InEffectTag.IsValid()) { - return 0; + RemoveEffectByIdSafe({ Effect->EffectData.EffectID }, QueueType); +} + +TArray UGMC_AbilitySystemComponent::EffectsMatchingTag(const FGameplayTag& Tag, int32 NumToRemove) const +{ + if (NumToRemove < -1 || !Tag.IsValid()) { + return {}; } - TMap EffectsToRemove; - int32 NumRemoved = 0; + TArray EffectsToRemove; + int NumRemoved = 0; for(const TTuple Effect : ActiveEffects) { @@ -1296,37 +1662,50 @@ int32 UGMC_AbilitySystemComponent::RemoveEffectByTag(FGameplayTag InEffectTag, i break; } - if(Effect.Value->EffectData.EffectTag.IsValid() && Effect.Value->EffectData.EffectTag.MatchesTagExact(InEffectTag)){ - EffectsToRemove.Add(Effect.Key, Effect.Value); + if(Effect.Value->EffectData.EffectTag.IsValid() && Effect.Value->EffectData.EffectTag.MatchesTagExact(Tag)){ + EffectsToRemove.Add(Effect.Value->EffectData.EffectID); NumRemoved++; } } - - if (bOuterActivation) { - if (HasAuthority() && EffectsToRemove.Num() > 0) { + return EffectsToRemove; +} - TArray EffectIDsToRemove; - for (const auto& ToRemove : EffectsToRemove) { - EffectIDsToRemove.Add(ToRemove.Key); - } - - FGMCOuterApplicationWrapper Wrapper = FGMCOuterApplicationWrapper::Make(EffectIDsToRemove); - AddPendingEffectApplications(Wrapper); - } +int32 UGMC_AbilitySystemComponent::RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove, bool bOuterActivation) { + + if (NumToRemove < -1 || !InEffectTag.IsValid()) { return 0; } - for (auto& ToRemove : EffectsToRemove) { - ToRemove.Value->EndEffect(); + TArray EffectsToRemove = EffectsMatchingTag(InEffectTag, NumToRemove); + + if (EffectsToRemove.Num() > 0) + { + RemoveEffectById(EffectsToRemove, bOuterActivation); } - - return NumRemoved; + + return EffectsToRemove.Num(); } +int32 UGMC_AbilitySystemComponent::RemoveEffectByTagSafe(FGameplayTag InEffectTag, int32 NumToRemove, + EGMCAbilityEffectQueueType QueueType) +{ + if (NumToRemove < -1 || !InEffectTag.IsValid()) { + return 0; + } -bool UGMC_AbilitySystemComponent::RemoveEffectById(TArray Ids, bool bOuterActivation) { + TArray EffectsToRemove = EffectsMatchingTag(InEffectTag, NumToRemove); + + if (EffectsToRemove.Num() > 0) + { + RemoveEffectByIdSafe(EffectsToRemove, QueueType); + } + + return EffectsToRemove.Num(); +} +bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbilityEffectQueueType QueueType) +{ if (!Ids.Num()) { return true; } @@ -1334,26 +1713,98 @@ bool UGMC_AbilitySystemComponent::RemoveEffectById(TArray Ids, bool bOuterA // check all IDs exists for (int Id : Ids) { if (!ActiveEffects.Contains(Id)) { - UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Trying to remove effect with ID %d, but it doesn't exist!"), Id); + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("[%20s] %s tried to remove effect with ID %d, but it doesn't exist!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Id); return false; } } - if (bOuterActivation) { - if (HasAuthority()) { - FGMCOuterApplicationWrapper Wrapper = FGMCOuterApplicationWrapper::Make(Ids); - AddPendingEffectApplications(Wrapper); + switch(QueueType) + { + case EGMCAbilityEffectQueueType::Predicted: + { + if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) + return false; + } + + for (auto& Effect : ActiveEffects) { + if (Ids.Contains(Effect.Key)) { + RemoveActiveAbilityEffect(Effect.Value); + } + } + + return true; + } + case EGMCAbilityEffectQueueType::PredictedQueued: + case EGMCAbilityEffectQueueType::ClientAuth: + { + if (QueueType == EGMCAbilityEffectQueueType::ClientAuth) + { + if (GetNetMode() != NM_Standalone && (HasAuthority() && !GMCMovementComponent->IsLocallyControlledServerPawn())) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) + return false; + } + } + + TGMASBoundQueueOperation Operation; + FGMCAbilityEffectData Data; + QueuedEffectOperations_ClientAuth.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); + QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ClientAuth); + return true; } + case EGMCAbilityEffectQueueType::ServerAuthMove: + case EGMCAbilityEffectQueueType::ServerAuth: + { + if (!HasAuthority()) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) + return false; + } + + TGMASBoundQueueOperation Operation; + FGMCAbilityEffectData Data; + QueuedEffectOperations.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); + QueuedEffectOperations.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ServerAuthMove); + + if (QueueType == EGMCAbilityEffectQueueType::ServerAuth) + { + // Send the operation over to our client via standard RPC. + ClientQueueEffectOperation(Operation); + } + } + return true; } + + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) + return false; +} - for (auto& Effect : ActiveEffects) { - if (Ids.Contains(Effect.Key)) { - Effect.Value->EndEffect(); - } +bool UGMC_AbilitySystemComponent::RemoveEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType) +{ + int32 EffectID; + UGMCAbilityEffect* Effect; + if (GetEffectFromHandle(EffectHandle, EffectID, Effect) && EffectID > 0) + { + return RemoveEffectByIdSafe({ EffectID }, QueueType); } + + return false; +} + + +bool UGMC_AbilitySystemComponent::RemoveEffectById(TArray Ids, bool bOuterActivation) { + + // Just hit up the newer version. + return RemoveEffectByIdSafe(Ids, bOuterActivation ? EGMCAbilityEffectQueueType::ServerAuth : EGMCAbilityEffectQueueType::Predicted); - return true; } @@ -1460,7 +1911,7 @@ FString UGMC_AbilitySystemComponent::GetAllAttributesString() const{ } FString UGMC_AbilitySystemComponent::GetActiveEffectsDataString() const{ - FString FinalString = TEXT("\n"); + FString FinalString = FString::Printf(TEXT("%d total\n"), ActiveEffectsData.Num()); for(const FGMCAbilityEffectData& ActiveEffectData : ActiveEffectsData){ FinalString += ActiveEffectData.ToString() + TEXT("\n"); } @@ -1468,7 +1919,7 @@ FString UGMC_AbilitySystemComponent::GetActiveEffectsDataString() const{ } FString UGMC_AbilitySystemComponent::GetActiveEffectsString() const{ - FString FinalString = TEXT("\n"); + FString FinalString = FString::Printf(TEXT("%d total\n"), ActiveEffects.Num()); for(const TTuple ActiveEffect : ActiveEffects){ FinalString += ActiveEffect.Value->ToString() + TEXT("\n"); } diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index f2d0f280..992731f8 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -74,7 +74,7 @@ void UGMCAbilityEffect::StartEffect() { for (const FGMCAttributeModifier& Modifier : EffectData.Modifiers) { - OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, true); + OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, true, false, SourceAbilityComponent); } EndEffect(); return; @@ -86,7 +86,7 @@ void UGMCAbilityEffect::StartEffect() EffectData.bNegateEffectAtEnd = true; for (const FGMCAttributeModifier& Modifier : EffectData.Modifiers) { - OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, false); + OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, false, false, SourceAbilityComponent); } } @@ -128,7 +128,7 @@ void UGMCAbilityEffect::EndEffect() } } - RemoveTagsFromOwner(); + RemoveTagsFromOwner(EffectData.bPreserveGrantedTagsIfMultiple); RemoveAbilitiesFromOwner(); } @@ -201,7 +201,7 @@ void UGMCAbilityEffect::PeriodTick() if (AttributeDynamicCondition()) { for (const FGMCAttributeModifier& AttributeModifier : EffectData.Modifiers) { - OwnerAbilityComponent->ApplyAbilityEffectModifier(AttributeModifier, true); + OwnerAbilityComponent->ApplyAbilityEffectModifier(AttributeModifier, true, false, SourceAbilityComponent); } } } @@ -231,14 +231,22 @@ void UGMCAbilityEffect::AddTagsToOwner() void UGMCAbilityEffect::RemoveTagsFromOwner(bool bPreserveOnMultipleInstances) { - - if (bPreserveOnMultipleInstances && EffectData.EffectTag.IsValid()) { - TArray ActiveEffect = OwnerAbilityComponent->GetActivesEffectByTag(EffectData.EffectTag); - - if (ActiveEffect.Num() > 1) { - return; + if (bPreserveOnMultipleInstances) + { + if (EffectData.EffectTag.IsValid()) { + TArray ActiveEffect = OwnerAbilityComponent->GetActiveEffectsByTag(EffectData.EffectTag); + + if (ActiveEffect.Num() > 1) { + return; + } + } + else + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Effect Tag is not valid with PreserveMultipleInstances in UGMCAbilityEffect::RemoveTagsFromOwner")); } } + + for (const FGameplayTag Tag : EffectData.GrantedTags) { diff --git a/Source/GMCAbilitySystem/Private/Utility/GMASBoundQueue.cpp b/Source/GMCAbilitySystem/Private/Utility/GMASBoundQueue.cpp new file mode 100644 index 00000000..1a3be8b2 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Utility/GMASBoundQueue.cpp @@ -0,0 +1 @@ +#include "Utility/GMASBoundQueue.h" diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 42ec16ea..b12c7511 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -11,7 +11,7 @@ #include "Ability/Tasks/GMCAbilityTaskData.h" #include "Effects/GMCAbilityEffect.h" #include "Components/ActorComponent.h" -#include "GMCAbilityOuterApplication.h" +#include "Utility/GMASBoundQueue.h" #include "GMCAbilityComponent.generated.h" @@ -28,8 +28,6 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAncillaryTick, float, DeltaTime); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActiveTagsChanged, FGameplayTagContainer, AddedTags, FGameplayTagContainer, RemovedTags); DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, const FGameplayTagContainer&, const FGameplayTagContainer&); - - USTRUCT() struct FEffectStatePrediction { @@ -44,6 +42,45 @@ struct FEffectStatePrediction uint8 State; }; +USTRUCT() +struct FGMASQueueOperationHandle +{ + GENERATED_BODY() + + UPROPERTY() + int32 Handle { -1 }; + + UPROPERTY() + int32 OperationId { -1 }; + + UPROPERTY() + int32 NetworkId { -1 }; +}; + +UENUM(BlueprintType) +enum class EGMCAbilityEffectQueueType : uint8 +{ + /// Immediately applied, only valid within the GMC movement cycle. Should be applied on both client and server. + Predicted UMETA(DisplayName="Predicted"), + + /// Predicted effect, not replicated but will be queued for addition in the next GMC movement cycle. Valid even + /// outside of the GMC movement cycle. Should be applied on both client and server. If used during the GMC + /// movement cycle, this is silently turned into Predicted. + PredictedQueued UMETA(DisplayName="Predicted [Queued]"), + + /// Only valid on server; queued from server and sent to client via RPC. Valid even outside of the GMC movement cycle. + ServerAuth UMETA(DisplayName="Server Auth"), + + /// Only valid on client; queued from client and sent to the server via GMC bindings. Valid even outside of the + /// GMC movement cycle. You almost certainly don't want to use this, but it's here for the sake of completeness. + ClientAuth UMETA(Hidden, DisplayName="Client Auth"), + + /// Only valid on server; queued from server and recorded in the GMC move history. Valid even outside of the GMC + /// movement cycle. Slower than ServerAuth, only use this if you really need to preserve the effect application in + /// the movement history. you almost certainly don't want to use this, but it's here for the sake of completeness. + ServerAuthMove UMETA(Hidden, DisplayName="ADVANCED: Server Auth [Movement Cycle]") +}; + class UGMCAbility; UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent, DisplayName="GMC Ability System Component"), meta=(Categories="GMAS")) @@ -78,7 +115,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Return active Effect with tag UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") - TArray GetActivesEffectByTag(FGameplayTag GameplayTag) const; + TArray GetActiveEffectsByTag(FGameplayTag GameplayTag) const; // Get the first active effect with the Effecttag UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") @@ -221,38 +258,95 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void CheckUnBoundAttributeChanges(); + int GetNextAvailableEffectID() const; + bool CheckIfEffectIDQueued(int EffectID) const; + int CreateEffectOperation(TGMASBoundQueueOperation& OutOperation, const TSubclassOf& Effect, const FGMCAbilityEffectData& EffectData, bool bForcedEffectId = true, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + /** - * Applies an effect to the Ability Component + * Applies an effect to the Ability Component. If bOuterActivation is false, the effect will be immediately + * applied; if either is true, the operation will be queued but no valid effect will be returned. If + * Outer Activation is true, the effect *must* be applied on the server. * * @param Effect Effect to apply - * @param AdditionalModifiers Additional Modifiers to apply with this effect application - * @param SourceAbilityComponent Ability Component from which this effect originated - * @param bOverwriteExistingModifiers Whether or not to replace existing modifiers that have the same name as additional modifiers. If false, will add them. - * @param bAppliedByServer Is this Effect only applied by server? Used to help client predict the unpredictable. + * @param InitializationData Effect initialization data. + * @param bOuterActivation Whether this effect should be replicated outside of GMC, via normal Unreal RPC */ - UFUNCTION(BlueprintCallable, Category="GMAS|Effects", meta = (AutoCreateRefTerm = "AdditionalModifiers")) + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Apply Ability Effect (Legacy)", meta=(DeprecatedFunction, DeprecationMessage="Please use the more modern ApplyAbilityEffect which takes a queue type.")) UGMCAbilityEffect* ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData, bool bOuterActivation = false); + + // BP-specific version of + /** + * Applies an effect to the ability component. If the Queue Type is Predicted, the effect will be immediately added + * on both client and server; this must happen within the GMC movement lifecycle for it to be valid. If the + * Queue Type is anything else, the effect must be queued on the server and will be replicated to the client. + */ + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Apply Ability Effect") + void ApplyAbilityEffectSafe(TSubclassOf EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, + UPARAM(DisplayName="Success") bool& OutSuccess, UPARAM(DisplayName="Effect Handle") int& OutEffectHandle, UPARAM(DisplayName="Effect Network ID") int& OutEffectId, UPARAM(DisplayName="Effect Instance") UGMCAbilityEffect*& OutEffect); + + /** + * Applies an effect to the ability component. If the Queue Type is Predicted, the effect will be immediately added + * on both client and server; this must happen within the GMC movement lifecycle for it to be valid. If the + * Queue Type is anything else, the effect must be queued on the server and will be replicated to the client. + * + * @param EffectClass The class of ability effect to add. + * @param InitializationData The initialization data for the ability effect. + * @param QueueType How to queue the effect. + * @param OutEffectHandle A local handle to this effect, only valid locally. + * @param OutEffectId The newly-created effect's network ID, if one is available. Valid across server/client. + * @param OutEffect The newly-created effect instance, if available. + * @return true if the effect was applied, false otherwise. + */ + bool ApplyAbilityEffect(TSubclassOf EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectHandle, int& OutEffectId, UGMCAbilityEffect*& OutEffect); + + // Do not call this directly unless you know what you are doing. Otherwise, always go through the above ApplyAbilityEffect variant! UGMCAbilityEffect* ApplyAbilityEffect(UGMCAbilityEffect* Effect, FGMCAbilityEffectData InitializationData); - + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + UGMCAbilityEffect* GetEffectById(const int EffectId) const; + + TArray EffectsMatchingTag(const FGameplayTag& Tag, int32 NumToRemove = -1) const; + + // Do not call this directly unless you know what you are doing; go through the RemoveActiveAbilityEffectSafe if + // doing this from outside of the component, to allow queuing and sanity-check. UFUNCTION(BlueprintCallable, Category="GMAS|Effects") void RemoveActiveAbilityEffect(UGMCAbilityEffect* Effect); + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Active Ability Effect (Safe)") + void RemoveActiveAbilityEffectSafe(UGMCAbilityEffect* Effect, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + /** * Removes an instanced effect if it exists. If NumToRemove == -1, remove all. Returns the number of removed instances. * If the inputted count is higher than the number of active corresponding effects, remove all we can. */ - UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effect by Tag (Legacy)", meta=(DeprecatedFunction, DeprecationMessage="Please use the more modern RemoveEffectByTagSafe which takes a queue type.")) int32 RemoveEffectByTag(FGameplayTag InEffectTag, int32 NumToRemove=-1, bool bOuterActivation = false); + /** + * Removes an instanced effect if it exists. If NumToRemove == -1, remove all. Returns the number of removed instances. + * If the inputted count is higher than the number of active corresponding effects, remove all we can. + */ + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effects by Tag (Safe)") + int32 RemoveEffectByTagSafe(FGameplayTag InEffectTag, int32 NumToRemove=-1, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + /** * Removes an instanced effect by ids. * return false if any of the ids are invalid. */ - UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effects by Id (Legacy)", meta=(DeprecatedFunction, DeprecationMessage="Please use the more modern RemoveEffectByIdSafe which takes a queue type.")) bool RemoveEffectById(TArray Ids, bool bOuterActivation = false); + /** + * Removes an instanced effect by ids. + * return false if any of the ids are invalid. + */ + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effects by Id (Safe)") + bool RemoveEffectByIdSafe(TArray Ids, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effect by Handle") + bool RemoveEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType); + /** * Gets the number of active effects with the inputted tag. * Returns -1 if tag is invalid. @@ -366,6 +460,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS") virtual void PreLocalMoveExecution(); + UFUNCTION(BlueprintCallable, Category="GMAS") + virtual void PreRemoteMoveExecution(); + #pragma endregion GMC #pragma region ToStringHelpers @@ -440,12 +537,21 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Add the starting ability tags to GrantedAbilities at start void InitializeStartingAbilities(); - TArray QueuedAbilities; TArray QueuedTaskData; - // Current Ability Data being processed - // Members of this struct are bound over GMC - // FGMCAbilityData AbilityData; + // Queued ability operations (activate, cancel, etc.) + TGMASBoundQueue QueuedAbilityOperations; + bool ProcessAbilityOperation(const TGMASBoundQueueOperation& Operation, bool bFromMovementTick); + + TGMASBoundQueue QueuedEffectOperations; + TGMASBoundQueue QueuedEffectOperations_ClientAuth; + UGMCAbilityEffect* ProcessEffectOperation(const TGMASBoundQueueOperation& Operation); + + bool ShouldProcessEffectOperation(const TGMASBoundQueueOperation& Operation, bool bIsServer = true) const; + void ClientQueueEffectOperation(const TGMASBoundQueueOperation& Operation); + + UFUNCTION(Client, Reliable) + void RPCClientQueueEffectOperation(const FGMASBoundQueueRPCHeader& Header); // Predictions of Effect state changes FEffectStatePrediction EffectStatePrediction{}; @@ -461,8 +567,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY() TMap ActiveCooldowns; - - + int GenerateAbilityID() const {return ActionTimer * 100;} // Set Attributes to either a default object or a provided TSubClassOf in BP defaults @@ -513,32 +618,28 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY() TMap ActiveEffects; - // Effect applied externally, pending activation, used by server and client. Not replicated. - //TODO: Later we will need to encapsulate this with Instanced struct to have a more generic way to handle this, and have cohabitation server <-> client UPROPERTY() - TArray PendingApplicationServer; + TMap EffectHandles; - UPROPERTY() - TArray PendingApplicationClient; + int GetNextAvailableEffectHandle() const; + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + void GetEffectFromHandle_BP(int EffectHandle, bool& bOutSuccess, int32& OutEffectNetworkId, UGMCAbilityEffect*& OutEffect); + + bool GetEffectFromHandle(int EffectHandle, int32& OutEffectNetworkId, UGMCAbilityEffect*& OutEffect) const; + bool GetEffectHandle(int EffectHandle, FGMASQueueOperationHandle& HandleData) const; + + void RemoveEffectHandle(int EffectHandle); + // doesn't work ATM. UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem", meta=(AllowPrivateAccess="true")) bool bInGMCTime = false; - // TODO: Need to be pushed later on a int64 32 index + 32 bitfield - // Binded Used for acknowledge server initiated ability/effect - FInstancedStruct AcknowledgeId = FInstancedStruct::Make(FGMCAcknowledgeId{}); - - void AddPendingEffectApplications(FGMCOuterApplicationWrapper& Wrapper); - // Let the client know that the server ask for an external effect application - UFUNCTION(Client, Reliable) - void RPCClientAddPendingEffectApplication(FGMCOuterApplicationWrapper Wrapper); - void ServerHandlePendingEffect(float DeltaTime); + void ServerHandlePredictedPendingEffect(float DeltaTime); void ClientHandlePendingEffect(); - - int GenerateLateApplicationID(); + void ClientHandlePredictedPendingEffect(); int LateApplicationIDCounter = 0; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h deleted file mode 100644 index 85bffac5..00000000 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "InstancedStruct.h" -#include "GMCAbilityOuterApplication.generated.h" - -USTRUCT() -struct FGMCAcknowledgeId { - GENERATED_BODY() - - UPROPERTY() - TArray Id = {}; -}; - - -UENUM() -enum EGMCOuterApplicationType { - EGMC_AddEffect, - EGMC_RemoveEffect, -}; - -USTRUCT(BlueprintType) -struct FGMCOuterEffectAdd { - GENERATED_BODY() - - UPROPERTY() - TSubclassOf EffectClass; - - UPROPERTY() - FGMCAbilityEffectData InitializationData; -}; - -USTRUCT(BlueprintType) -struct FGMCOuterEffectRemove { - GENERATED_BODY() - - UPROPERTY() - TArray Ids = {}; -}; - -USTRUCT(BlueprintType) -struct FGMCOuterApplicationWrapper { - GENERATED_BODY() - - UPROPERTY() - TEnumAsByte Type = EGMC_AddEffect; - - UPROPERTY() - FInstancedStruct OuterApplicationData; - - UPROPERTY() - int LateApplicationID = 0; - - float ClientGraceTimeRemaining = 0.f; - - template - static FGMCOuterApplicationWrapper Make(Args... args) - { - FGMCOuterApplicationWrapper Wrapper; - Wrapper.OuterApplicationData = FInstancedStruct::Make(args...); - return Wrapper; - } - - -}; - -template<> inline FGMCOuterApplicationWrapper FGMCOuterApplicationWrapper::Make(TSubclassOf Effect, FGMCAbilityEffectData InitializationData) -{ - FGMCOuterApplicationWrapper Wrapper; - Wrapper.Type = EGMC_AddEffect; - Wrapper.OuterApplicationData = FInstancedStruct::Make(); - FGMCOuterEffectAdd& Data = Wrapper.OuterApplicationData.GetMutable(); - Data.EffectClass = Effect; - Data.InitializationData = InitializationData; - return Wrapper; -} - -template<> inline FGMCOuterApplicationWrapper FGMCOuterApplicationWrapper::Make(TArray Ids) -{ - FGMCOuterApplicationWrapper Wrapper; - Wrapper.Type = EGMC_RemoveEffect; - Wrapper.OuterApplicationData = FInstancedStruct::Make(); - FGMCOuterEffectRemove& Data = Wrapper.OuterApplicationData.GetMutable(); - Data.Ids = Ids; - return Wrapper; -} diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index d631ac30..340ee8d9 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -108,6 +108,11 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedTags; + // Whether to preserve the granted tags if multiple instances of the same effect are applied + // If false, will remove all stacks of the tag + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") + bool bPreserveGrantedTagsIfMultiple = false; + // Tags that the owner must have to apply this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer ApplicationMustHaveTags; diff --git a/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h b/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h new file mode 100644 index 00000000..fb91525a --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h @@ -0,0 +1,582 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GMCMovementUtilityComponent.h" +#include "InstancedStruct.h" +#include "UObject/Object.h" +#include "UObject/ConstructorHelpers.h" +#include "GMASBoundQueue.generated.h" + +UENUM(BlueprintType) +enum class EGMASBoundQueueOperationType : uint8 +{ + None, + Add, + Remove, + Activate, + Cancel +}; + +USTRUCT(BlueprintType) +struct FGMASBoundQueueOperationIdSet +{ + GENERATED_BODY() + + UPROPERTY() + TArray Ids = {}; +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FGMASBoundQueueRPCHeader +{ + GENERATED_BODY() + + UPROPERTY() + int32 OperationId { -1 }; + + UPROPERTY() + uint8 OperationTypeRaw { 0 }; + + EGMASBoundQueueOperationType GetOperationType() const + { + return static_cast(OperationTypeRaw); + } + + UPROPERTY() + FGameplayTag Tag { FGameplayTag::EmptyTag }; + + UPROPERTY() + FName ItemClassName { NAME_None }; + + UPROPERTY() + FGMASBoundQueueOperationIdSet PayloadIds {}; + + UPROPERTY() + FInstancedStruct InstancedPayload {}; + + UPROPERTY() + float RPCGracePeriodSeconds { 1.f }; + + UPROPERTY() + uint8 ExtraFlags { 0 }; +}; + +template +struct GMCABILITYSYSTEM_API TGMASBoundQueueOperation +{ + FGMASBoundQueueRPCHeader Header {}; + + // An actual class to be utilized with this, in case we need to instance it. + TSubclassOf ItemClass { nullptr }; + + FInstancedStruct InstancedPayloadIds; + + // The struct payload for this item. + T Payload; + + // The instanced struct representation of this payload, used to actually + // bind for replication. + FInstancedStruct InstancedPayload; + + EGMASBoundQueueOperationType GetOperationType() const + { + return Header.GetOperationType(); + } + + void SetOperationType(EGMASBoundQueueOperationType OperationType) + { + Header.OperationTypeRaw = static_cast(OperationType); + } + + int32 GetOperationId() const { return Header.OperationId; } + + TArray GetPayloadIds() const { return Header.PayloadIds.Ids; } + + FGameplayTag GetTag() const { return Header.Tag; } + + bool GracePeriodExpired() const + { + return Header.RPCGracePeriodSeconds <= 0.f; + } + + bool IsValid() const + { + return Header.OperationTypeRaw != 0 && (Header.ItemClassName != NAME_None || Header.Tag != FGameplayTag::EmptyTag || Header.PayloadIds.Ids.Num() > 0); + } + + void Refresh(bool bDecodePayload = false) + { + if (bDecodePayload) + { + // Incoming from remote. + Payload = Header.InstancedPayload.Get(); + if (Header.PayloadIds.Ids.Num() == 0 && InstancedPayloadIds.IsValid()) + { + Header.PayloadIds = InstancedPayloadIds.Get(); + } + } + else + { + // Outgoing + Header.InstancedPayload = FInstancedStruct::Make(Payload); + InstancedPayloadIds = FInstancedStruct::Make(Header.PayloadIds); + } + + RefreshClass(); + } + + void RefreshClass() + { + if (Header.ItemClassName != NAME_None && !ItemClass) + { + // Get a handle to our class, for instancing purposes. + TSoftClassPtr ClassPtr = TSoftClassPtr(FSoftObjectPath(Header.ItemClassName.ToString())); + ItemClass = ClassPtr.LoadSynchronous(); + } + + if (ItemClass && Header.ItemClassName == NAME_None) + { + Header.ItemClassName = FName(ItemClass->GetPathName()); + } + } + +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FGMASBoundQueueAcknowledgement +{ + GENERATED_BODY() + + UPROPERTY() + int32 Id { -1 }; + + UPROPERTY() + float Lifetime { 5.f }; +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FGMASBoundQueueAcknowledgements +{ + GENERATED_BODY() + + UPROPERTY() + TArray AckSet; +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FGMASBoundQueueEmptyData +{ + GENERATED_BODY() +}; + +template +class GMCABILITYSYSTEM_API TGMASBoundQueue +{ + +public: + + TGMASBoundQueueOperation CurrentOperation; + + int BI_ActionTimer { -1 }; + int BI_Acknowledgements { -1 }; + int BI_OperationId { -1 }; + int BI_OperationType { -1 }; + int BI_OperationTag { -1 }; + int BI_OperationClass { -1 }; + int BI_OperationExtraFlags { -1 }; + int BI_OperationPayload { -1 }; + int BI_OperationPayloadIds { -1 }; + + TArray> QueuedBoundOperations; + TArray> QueuedRPCOperations; + + FInstancedStruct Acknowledgments; + + double ActionTimer { 0 }; + + void BindToGMC(UGMC_MovementUtilityCmp* MovementComponent) + { + const EGMC_PredictionMode Prediction = ClientAuth ? EGMC_PredictionMode::ClientAuth_Input : EGMC_PredictionMode::ServerAuth_Input_ClientValidated; + const EGMC_PredictionMode AckPrediction = ClientAuth ? EGMC_PredictionMode::ServerAuth_Output_ClientValidated : EGMC_PredictionMode::ClientAuth_Input; + + Acknowledgments = FInstancedStruct::Make(FGMASBoundQueueAcknowledgements()); + + // Our queue's action timer is always server-auth. + BI_ActionTimer = MovementComponent->BindDoublePrecisionFloat( + ActionTimer, + EGMC_PredictionMode::ServerAuth_Output_ClientValidated, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::None, + EGMC_InterpolationFunction::TargetValue + ); + + if (!ClientAuth) + { + // Acknowledgements are bound client-auth for server-auth effects. + BI_Acknowledgements = MovementComponent->BindInstancedStruct( + Acknowledgments, + AckPrediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::None, + EGMC_InterpolationFunction::TargetValue); + } + else + { + // For client-auth stuff, we bind the individual pieces of the queue. + // We will probably not use this often (if ever), but it exists just-in-case. + + BI_OperationId = MovementComponent->BindInt( + CurrentOperation.Header.OperationId, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + + BI_OperationType = MovementComponent->BindByte( + CurrentOperation.Header.OperationTypeRaw, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + + BI_OperationTag = MovementComponent->BindGameplayTag( + CurrentOperation.Header.Tag, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + + BI_OperationClass = MovementComponent->BindName( + CurrentOperation.Header.ItemClassName, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + + BI_OperationPayload = MovementComponent->BindInstancedStruct( + CurrentOperation.Header.InstancedPayload, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + + BI_OperationExtraFlags = MovementComponent->BindByte( + CurrentOperation.Header.ExtraFlags, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + + BI_OperationPayloadIds = MovementComponent->BindInstancedStruct( + CurrentOperation.InstancedPayloadIds, + Prediction, + EGMC_CombineMode::CombineIfUnchanged, + EGMC_SimulationMode::Periodic_Output, + EGMC_InterpolationFunction::TargetValue); + } + + CurrentOperation = TGMASBoundQueueOperation(); + CurrentOperation.Header = FGMASBoundQueueRPCHeader(); + CurrentOperation.Refresh(false); + } + + void PreLocalMovement() + { + if (QueuedBoundOperations.Num() > 0 && ClientAuth) + { + CurrentOperation = QueuedBoundOperations.Pop(); + CurrentOperation.Refresh(false); + } + } + + void PreRemoteMovement() + { + ClearCurrentOperation(); + if (QueuedBoundOperations.Num() > 0 && !ClientAuth) + { + CurrentOperation = QueuedBoundOperations.Pop(); + CurrentOperation.Refresh(false); + } + } + + int32 GenerateOperationId() const + { + int32 NewOperationId = ActionTimer * 100; + + TGMASBoundQueueOperation Operation; + + while (GetOperationById(NewOperationId, Operation)) + { + NewOperationId++; + } + return NewOperationId; + } + + void ClearCurrentOperation() + { + CurrentOperation.Header = FGMASBoundQueueRPCHeader(); + + // CurrentOperation.Header.OperationId = -1; + // CurrentOperation.Header.Tag = FGameplayTag::EmptyTag; + // CurrentOperation.Header.ItemClassName = NAME_None; + // CurrentOperation.SetOperationType(EGMASBoundQueueOperationType::None); + // CurrentOperation.Header.PayloadIds.Ids.Empty(); + + CurrentOperation.Payload = T(); + CurrentOperation.Refresh(false); + } + + void GenPredictionTick(float DeltaTime) + { + ActionTimer += DeltaTime; + ExpireStaleAcks(DeltaTime); + } + + int32 MakeOperation(TGMASBoundQueueOperation& NewOperation, EGMASBoundQueueOperationType Type, FGameplayTag Tag, const T& Payload, TArray PayloadIds = {}, TSubclassOf ItemClass = nullptr, float RPCGracePeriod = 1.f, uint8 ExtraFlags = 0) + { + NewOperation.Header.OperationId = GenerateOperationId(); + NewOperation.SetOperationType(Type); + NewOperation.Header.Tag = Tag; + NewOperation.Payload = Payload; + NewOperation.ItemClass = ItemClass; + NewOperation.Header.RPCGracePeriodSeconds = RPCGracePeriod; + NewOperation.Header.PayloadIds.Ids = PayloadIds; + NewOperation.Header.ExtraFlags = ExtraFlags; + + NewOperation.Refresh(false); + + return NewOperation.GetOperationId(); + } + + int32 MakeOperation(TGMASBoundQueueOperation& NewOperation, const FGMASBoundQueueRPCHeader& Header, const T& Payload) + { + NewOperation.Header = Header; + NewOperation.Payload = Payload; + NewOperation.Refresh(); + NewOperation.InstancedPayload = FInstancedStruct::Make(Payload); + NewOperation.InstancedPayloadIds = FInstancedStruct::Make(NewOperation.Header.PayloadIds); + + return NewOperation.GetOperationId(); + } + + void QueuePreparedOperation(TGMASBoundQueueOperation& NewOperation, bool bMovementSynced = true) + { + TGMASBoundQueueOperation TestOperation = TGMASBoundQueueOperation(); + + // Don't bother queueing it if it already exists. + if (GetOperationById(NewOperation.GetOperationId(), TestOperation)) return; + + if (bMovementSynced) + { + // This needs to be handled via GMC, so add it to our queue. + QueuedBoundOperations.Push(NewOperation); + } + else + { + QueuedRPCOperations.Push(NewOperation); + } + } + + int32 QueueOperation(TGMASBoundQueueOperation& NewOperation, EGMASBoundQueueOperationType Type, FGameplayTag Tag, const T& Payload, TArray PayloadIds = {}, TSubclassOf ItemClass = nullptr, bool bMovementSynced = true, float RPCGracePeriod = 1.f) + { + MakeOperation(NewOperation, Type, Tag, Payload, PayloadIds, ItemClass, RPCGracePeriod); + QueuePreparedOperation(NewOperation, bMovementSynced); + return NewOperation.GetOperationId(); + } + + void QueueOperationFromHeader(const FGMASBoundQueueRPCHeader& Header, bool bMovementSynced) + { + TGMASBoundQueueOperation NewOperation; + + NewOperation.Header = Header; + NewOperation.Refresh(true); + QueuePreparedOperation(NewOperation, bMovementSynced); + } + + int Num() const + { + return QueuedBoundOperations.Num(); + } + + int NumMatching(FGameplayTag Tag, EGMASBoundQueueOperationType Type = EGMASBoundQueueOperationType::None) const + { + int Result = 0; + for (const auto& Operation : QueuedBoundOperations) + { + if (Operation.GetTag() == Tag) + { + if (Type == EGMASBoundQueueOperationType::None || Operation.GetOperationType() == Type) Result++; + } + } + for (const auto& Operation : QueuedRPCOperations) + { + if (Operation.GetTag() == Tag) + { + if (Type == EGMASBoundQueueOperationType::None || Operation.GetOperationType() == Type) Result++; + } + } + return Result; + } + + const TArray>& GetQueuedOperations() const { return QueuedBoundOperations; } + + const TArray>& GetQueuedRPCOperations() const { return QueuedRPCOperations; } + + bool GetOperationById(int32 OperationId, TGMASBoundQueueOperation& OutOperation) const + { + for (const auto& Operation : GetQueuedOperations()) + { + if (Operation.GetOperationId() == OperationId) + { + OutOperation = Operation; + return true; + } + } + + for (const auto& Operation : GetQueuedRPCOperations()) + { + if (Operation.GetOperationId() == OperationId) + { + OutOperation = Operation; + return true; + } + } + + return false; + } + + bool HasOperationWithPayloadId(int32 PayloadId) const + { + for (const auto& Operation : QueuedBoundOperations) + { + if (Operation.GetPayloadIds().Contains(PayloadId)) + { + return true; + } + } + + for (const auto& Operation : QueuedRPCOperations) + { + if (Operation.GetPayloadIds().Contains(PayloadId)) + { + return true; + } + } + + return false; + } + + bool RemoveOperationById(int32 OperationId) + { + int TargetIdx = -1; + + for (int Idx = 0; Idx < QueuedRPCOperations.Num() && TargetIdx == -1; Idx++) + { + if(QueuedRPCOperations[Idx].GetOperationId() == OperationId) + { + TargetIdx = Idx; + } + } + + if (TargetIdx != -1) + { + QueuedRPCOperations.RemoveAtSwap(TargetIdx, 1, false); + return true; + } + + TargetIdx = -1; + for (int Idx = 0; Idx < QueuedBoundOperations.Num() && TargetIdx == -1; Idx++) + { + if(QueuedBoundOperations[Idx].GetOperationId() == OperationId) + { + TargetIdx = Idx; + } + } + + if (TargetIdx != -1) + { + QueuedBoundOperations.RemoveAtSwap(TargetIdx, 1, false); + return true; + } + + return false; + } + + bool GetCurrentBoundOperation(TGMASBoundQueueOperation& Operation, bool bRefresh = false) + { + Operation = CurrentOperation; + if (Operation.GetOperationType() != EGMASBoundQueueOperationType::None) + { + if (bRefresh) + { + Operation.Refresh(true); + } + else + { + Operation.RefreshClass(); + } + return true; + } + + return false; + } + + bool PopNextRPCOperation(TGMASBoundQueueOperation& Operation) + { + if (QueuedRPCOperations.Num() == 0) return false; + + Operation = QueuedRPCOperations.Pop(); + return true; + } + + void DeductGracePeriod(float DeltaTime) + { + for (auto& Operation : QueuedRPCOperations) + { + Operation.Header.RPCGracePeriodSeconds -= DeltaTime; + } + } + + void Acknowledge(int32 OperationId, float AckLifetime = 5.f) + { + if (!IsAcknowledged(OperationId)) + { + FGMASBoundQueueAcknowledgement NewAck; + NewAck.Id = OperationId; + NewAck.Lifetime = AckLifetime; + + auto& Acks = Acknowledgments.GetMutable(); + Acks.AckSet.Add(NewAck); + } + } + + bool IsAcknowledged(int32 OperationId) const + { + const auto& Acks = Acknowledgments.Get(); + for (const auto& Ack : Acks.AckSet) + { + if (Ack.Id == OperationId) return true; + } + return false; + } + + void ExpireStaleAcks(float DeltaTime) + { + // Deduct from our ack lifetime; if we've gone stale, remove the stale acks to avoid it just growing forever. + TArray FreshAcks; + auto& Acks = Acknowledgments.GetMutable(); + for (auto& Ack : Acks.AckSet) + { + Ack.Lifetime -= DeltaTime; + if (Ack.Lifetime > 0.f) + { + FreshAcks.Add(Ack); + } + } + Acks.AckSet = FreshAcks; + } + +}; From 0d653eb64b9b4b0cb3c8deba801e81dd4e39612e Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Wed, 18 Dec 2024 11:08:52 +0100 Subject: [PATCH 13/63] Deep Worlds SA - Stability Update (#100) * Add UPROPERTY Categories to allow compilation as engine plugin. * Adding Method CancelAbility, allowing ending an ability without triggering EndAbilityEvent. Internally, FinishEndAbility Method as also been added and ensure logics (see GAS termination of an ability) * Moving Activation tag requirement check before instantiation of the ability. * Added pre-check function for ability, allowing to have some basic test and allow to cancel an ability if they fail, Overridable in C++ or in Blueprint. * Adding Precheck and Cancelling by tag - Adding PreCheck function allowing to test logic on the start of an ability - Adding Cancelling ability by tag. * Added Activity Blocked by ActiveAbility * Fixing crash in HandleTaskHeartBeat. * Change behavior for attribute ChangeCallback - Callback OnAttributeChanged is now also called on SP - Callback is now also called when the value is affected outside for whatever reason (like by SetAttributeValueByTag) - Support Replay - SetAttributeValueByTag now also re-caculate the value. Signed-off-by: Eric Rajot * Change behavior for attribute ChangeCallback - Callback OnAttributeChanged is now also called on SP/AP - Callback is now also called when the value is affected outside for whatever reason (like by SetAttributeValueByTag) - Support Replay - SetAttributeValueByTag now also re-caculate the value. Signed-off-by: Eric Rajot * BL-279 Clean before merge - Removed Log message - Removed irrelevant bounding Signed-off-by: Eric Rajot * BL-279 Bug fixing and improvement for rep notify * BL-279 Fixing Attribute notification * BL-279 Adding Byte data type to Set Target for DW GMC Ability * Improvement for tags, added duration * BL-232 Progress * BL-232 Initial work on External Effect/Ability Pending and Tag application * BL-232 Working state. * BL-232 Refactor and cleaning, handled now by Instanced Struct * BL-232 Progress of the day * Fixing a bug in remove effect. They are now removing by specifique ID when outer removed, to ensure the list rest coherent client <-> server * BL-232 Fixing removing effect * BL-232 bug fixing in effect * Bug fixing, adding accessor * BL-232 Fix effect remove itself even is another instance is running * Added getter * Fixed name space for SetTargetDataFloat, Fixed EEffectType of GMCAbilityEffect sharing same name than playfab, Fixed wait for input key release with suggestion of Nas * Stability - Fixed name space for SetTargetDataFloat, - Fixed EEffectType of GMCAbilityEffect sharing same name than playfab - Fixed wait for input key release with suggestion of Nas - GetAbilityMapData now return a const ref. For compability, you probably want to add to core redirect those lines : ``` +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectType",NewName="/Script/GMCAbilitySystem.EGMASEffectType") +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectState",NewName="/Script/GMCAbilitySystem.EGMASEffectState") ``` * Adding possibility for effect to end an active ability * Changing Ability Blocking way. They are now internally stored. An Ability store itself what ability it will block, instead of other ability who will block him. This allow to modify during execution this behavior. For example, you may be want to stop an ability activation only during X time in your ability execution. * Adding a nicer way to end ability * BL-225 Grenade 100% * Fix clang error * Adding Attribute Dynamic Condition to effect. * Fix crash when an active effect has been destroyed * Module upload * GMC Update * Addition of levitation actor * Crash fix * GMC Fix for starting abilities, Fix for stamina, Fix for crash, Adding speed for orb, Adding plugin for metahuman hairs. * Update for log * Fix for GetActiveEffect ? * Typo fix * Removing unecessary log * Beginnings of general queue implementation * Further work on bound queues. * Convert abilities over to using the new bound operations queue. * Refactor to generalize ability queue processing * Convert effects to bound queue * Refactor and cleanup * A little more refactoring * Support server-auth effects queued via GMC moves. Note that this requires adding an SV_PreRemoteMoveExecution to your movement component, and calling into GMAS's PreRemoteMove call in that. * Ensure that Queue-via-GMC effects work in standalone as well. * Queue rework and cleanup finished! * Last few tweaks to the queue setup. * Further queue refactor and cleanup. New queue types (ClientAuth, PredictedQueued) added. * Add effect handles, so that PredictedQueued works right. * Small cleanup on effect handle expiry. * Correct desync for server-auth queue. * Fix crash BL-SERVER-B * Ensure we don't use the same ID if queuing two server-auth things in the same frame. * Fix for Reznok's two-effects-in-one-frame issue. * Correct issue with 2+ PredictedQueued operations in one tick, or 3+ ServerAuth queued. * BL-453 Adding application must have tag. * Fix for server compile * BL-453 - Fixing AbilityData unset from ability instanciation * Add Todo. * - Allowing Predicted effect in Ancilary tick. * [TO TEST] Moving tick of Actives Effect from Ancilarity to Predicted. Should fix the chain rollback with attribute * Fixing Crash * Re-added BL-453 Commit : fix for AbilitytData input activation tag missing from ability instanciation * Fixing crash in ability task data * Added EnsureMsfgIf to easier catch miss effect for users. TODO: Add effect tag. * BL-527 : Fixing nested tag removal in active tag. Issue reported as #98 in Reznok branch (https://github.com/reznok/GMCAbilitySystem/issues/98) -------------------------------------------- Issue was GetActiveEffectByTag() was looking also for depth in tag hierarchy. Fixed by adding an optional argument (bMatchExact), this argument has been set by default to true, and can mess with project who are calling this function if they are excepting in depht. Not ideal for retrocompability, but i prefere to enforce a good practice over the time. --------- Signed-off-by: Eric Rajot Co-authored-by: Younes Meziane Co-authored-by: Rachel Blackman Co-authored-by: Rachel Blackman --- .../Private/Ability/GMCAbility.cpp | 1 + .../Components/GMCAbilityComponent.cpp | 195 +++++++++--------- .../Private/Effects/GMCAbilityEffect.cpp | 12 +- .../Public/Components/GMCAbilityComponent.h | 14 +- .../Public/Effects/GMCAbilityEffect.h | 23 ++- 5 files changed, 121 insertions(+), 124 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index c4fa30a7..2e1670e1 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -78,6 +78,7 @@ void UGMCAbility::AncillaryTickTasks(float DeltaTime){ void UGMCAbility::Execute(UGMC_AbilitySystemComponent* InAbilityComponent, int InAbilityID, const UInputAction* InputAction) { + // TODO : Add input action tag here to avoid going by the old FGMCAbilityData struct this->AbilityInputAction = InputAction; this->AbilityID = InAbilityID; this->OwnerAbilityComponent = InAbilityComponent; diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index e36a892f..c369ba90 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -46,22 +46,12 @@ FDelegateHandle UGMC_AbilitySystemComponent::AddFilteredTagChangeDelegate(const void UGMC_AbilitySystemComponent::RemoveFilteredTagChangeDelegate(const FGameplayTagContainer& Tags, FDelegateHandle Handle) { - if (!Handle.IsValid()) - { - UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Passed an invalid delegate to unbind for tag changes on %s"), *Tags.ToString()) - return; - } - for (int32 Index = FilteredTagDelegates.Num() - 1; Index >= 0; --Index) { TPair& SearchPair = FilteredTagDelegates[Index]; if (SearchPair.Key == Tags) { - if (!SearchPair.Value.Remove(Handle)) - { - UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Unable to unbind a tag change delegate for %s"), *Tags.ToString()) - } - + SearchPair.Value.Remove(Handle); if (!SearchPair.Value.IsBound()) { FilteredTagDelegates.RemoveAt(Index); @@ -164,6 +154,9 @@ void UGMC_AbilitySystemComponent::BindReplicationData() } void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsCombinedClientMove) { + // Caution if you override Ancillarytick, this value should wrap up the override. + bInAncillaryTick = true; + OnAncillaryTick.Broadcast(DeltaTime); ClientHandlePendingEffect(); @@ -172,10 +165,9 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb CheckActiveTagsChanged(); CheckAttributeChanged(); - TickActiveEffects(DeltaTime); + TickActiveCooldowns(DeltaTime); TickAncillaryActiveAbilities(DeltaTime); - // Check if we have a valid operation TGMASBoundQueueOperation Operation; @@ -188,16 +180,17 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb ClearAbilityAndTaskData(); QueuedEffectOperations_ClientAuth.ClearCurrentOperation(); - bInGMCTime = false; + + bInAncillaryTick = false; } -TArray UGMC_AbilitySystemComponent::GetActiveEffectsByTag(FGameplayTag GameplayTag) const +TArray UGMC_AbilitySystemComponent::GetActiveEffectsByTag(FGameplayTag GameplayTag, bool bMatchExact) const { TArray ActiveEffectsFound; for (const TTuple& EffectFound : ActiveEffects) { - if (IsValid(EffectFound.Value) && EffectFound.Value->EffectData.EffectTag.MatchesTag(GameplayTag)) { + if (IsValid(EffectFound.Value) && bMatchExact ? EffectFound.Value->EffectData.EffectTag.MatchesTagExact(GameplayTag) : EffectFound.Value->EffectData.EffectTag.MatchesTag(GameplayTag)) { ActiveEffectsFound.Add(EffectFound.Value); } } @@ -234,22 +227,6 @@ void UGMC_AbilitySystemComponent::RemoveAbilityMapData(UGMCAbilityMapData* Abili } } -void UGMC_AbilitySystemComponent::AddStartingEffects(TArray> EffectsToAdd) -{ - for (const TSubclassOf Effect : EffectsToAdd) - { - StartingEffects.AddUnique(Effect); - } -} - -void UGMC_AbilitySystemComponent::RemoveStartingEffects(TArray> EffectsToRemove) -{ - for (const TSubclassOf Effect : EffectsToRemove) - { - StartingEffects.Remove(Effect); - } -} - void UGMC_AbilitySystemComponent::GrantAbilityByTag(const FGameplayTag AbilityTag) { if (!GrantedAbilityTags.HasTagExact(AbilityTag)) @@ -332,12 +309,12 @@ void UGMC_AbilitySystemComponent::TryActivateAbilitiesByInputTag(const FGameplay const UGMCAbility* AbilityCDO = ActivatedAbility->GetDefaultObject(); if(AbilityCDO && bFromMovementTick == AbilityCDO->bActivateOnMovementTick){ UE_LOG(LogGMCAbilitySystem, VeryVerbose, TEXT("Trying to Activate Ability: %s from %s"), *GetNameSafe(ActivatedAbility), bFromMovementTick ? TEXT("Movement") : TEXT("Ancillary")); - TryActivateAbility(ActivatedAbility, InputAction); + TryActivateAbility(ActivatedAbility, InputAction, InputTag); } } } -bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOf ActivatedAbility, const UInputAction* InputAction) +bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOf ActivatedAbility, const UInputAction* InputAction, const FGameplayTag ActivationTag) { if (ActivatedAbility == nullptr) return false; @@ -363,12 +340,6 @@ bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOf(this, ActivatedAbility); Ability->AbilityData = AbilityData; + Ability->AbilityData.InputTag = ActivationTag; Ability->Execute(this, AbilityID, InputAction); ActiveAbilities.Add(AbilityID, Ability); @@ -550,6 +522,7 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) ApplyStartingEffects(); TickActiveAbilities(DeltaTime); + TickActiveEffects(DeltaTime); // Abilities CleanupStaleAbilities(); @@ -658,7 +631,6 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() Attribute.CalculateValue(); } - // We need to be non-const to ensure we can mark the item dirty. for (FAttribute& Attribute : UnBoundAttributes.Items) { Attribute.CalculateValue(); @@ -769,7 +741,7 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime) for (const TPair& Effect : ActiveEffects) { - if (!IsValid(Effect.Value)) { + if (!Effect.Value->IsValidLowLevel()) { UE_LOG(LogGMCAbilitySystem, Error, TEXT("Active Effect id %d is null or pending kill, removing from the list."), Effect.Key); CompletedActiveEffects.Push(Effect.Key); continue; @@ -937,7 +909,9 @@ void UGMC_AbilitySystemComponent::ServerHandlePredictedPendingEffect(float Delta void UGMC_AbilitySystemComponent::ClientHandlePendingEffect() { - // Handle our RPC effect operations. MoveCycle operations will be sent via RPC + printf("ClientHandlePendingEffect"); + +// Handle our RPC effect operations. MoveCycle operations will be sent via RPC // just like the Outer ones, but will be preserved in the movement history. auto RPCOperations = QueuedEffectOperations.GetQueuedRPCOperations(); for (auto& Operation : RPCOperations) { @@ -1048,7 +1022,7 @@ void UGMC_AbilitySystemComponent::ClearAbilityAndTaskData() { void UGMC_AbilitySystemComponent::SendTaskDataToActiveAbility(bool bFromMovement) { - const FGMCAbilityTaskData TaskDataFromInstance = TaskData.Get(); + const FGMCAbilityTaskData TaskDataFromInstance = TaskData.IsValid() ? TaskData.Get() : FGMCAbilityTaskData{}; if (TaskDataFromInstance != FGMCAbilityTaskData{} && /*safety check*/ TaskDataFromInstance.TaskID >= 0) { if (ActiveAbilities.Contains(TaskDataFromInstance.AbilityID) && ActiveAbilities[TaskDataFromInstance.AbilityID]->bActivateOnMovementTick == bFromMovement) @@ -1513,7 +1487,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfIsExecutingMove() && GetNetMode() != NM_Standalone) + if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone && !bInAncillaryTick) { UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted to apply predicted effect %d of type %s outside of a GMC move!"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), EffectID, *EffectClass->GetName()) @@ -1528,7 +1502,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfIsExecutingMove()) + if (GMCMovementComponent->IsExecutingMove() || bInAncillaryTick) { // We're in a move context, just add it directly rather than queuing. OutEffect = ProcessEffectOperation(Operation); @@ -1695,6 +1669,7 @@ int32 UGMC_AbilitySystemComponent::RemoveEffectByTagSafe(FGameplayTag InEffectTa } TArray EffectsToRemove = EffectsMatchingTag(InEffectTag, NumToRemove); + if (EffectsToRemove.Num() > 0) { @@ -1719,69 +1694,95 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil } } - switch(QueueType) - { - case EGMCAbilityEffectQueueType::Predicted: - { - if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone) + switch(QueueType) { + case EGMCAbilityEffectQueueType::Predicted: { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) - return false; + if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone && !bInAncillaryTick) + { + ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) + return false; + } + + for (auto& Effect : ActiveEffects) { + if (Ids.Contains(Effect.Key)) { + RemoveActiveAbilityEffect(Effect.Value); + } + } + + return true; } - - for (auto& Effect : ActiveEffects) { - if (Ids.Contains(Effect.Key)) { - RemoveActiveAbilityEffect(Effect.Value); + case EGMCAbilityEffectQueueType::PredictedQueued: + { + // If in move, silenttly remove the effect as predicted + if (GMCMovementComponent->IsExecutingMove() || bInAncillaryTick) + { + for (auto& Effect : ActiveEffects) { + if (Ids.Contains(Effect.Key)) { + RemoveActiveAbilityEffect(Effect.Value); + } + } + + } + else { + TGMASBoundQueueOperation Operation; + FGMCAbilityEffectData Data; + QueuedEffectOperations_ClientAuth.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); + QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, false); + } + return true; + } + case EGMCAbilityEffectQueueType::ClientAuth: + { + if (QueueType == EGMCAbilityEffectQueueType::ClientAuth) + { + if (GetNetMode() != NM_Standalone && (HasAuthority() && !GMCMovementComponent->IsLocallyControlledServerPawn())) + { + ensureMsgf(false, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) + return false; + } } + + TGMASBoundQueueOperation Operation; + FGMCAbilityEffectData Data; + QueuedEffectOperations_ClientAuth.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); + QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ClientAuth); + return true; } - - return true; - } - case EGMCAbilityEffectQueueType::PredictedQueued: - case EGMCAbilityEffectQueueType::ClientAuth: - { - if (QueueType == EGMCAbilityEffectQueueType::ClientAuth) + case EGMCAbilityEffectQueueType::ServerAuthMove: + case EGMCAbilityEffectQueueType::ServerAuth: { - if (GetNetMode() != NM_Standalone && (HasAuthority() && !GMCMovementComponent->IsLocallyControlledServerPawn())) + if (!HasAuthority()) { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server!"), + ensureMsgf(false, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client!"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) return false; } - } - - TGMASBoundQueueOperation Operation; - FGMCAbilityEffectData Data; - QueuedEffectOperations_ClientAuth.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); - QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ClientAuth); - return true; - } - case EGMCAbilityEffectQueueType::ServerAuthMove: - case EGMCAbilityEffectQueueType::ServerAuth: - { - if (!HasAuthority()) - { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) - return false; - } - - TGMASBoundQueueOperation Operation; - FGMCAbilityEffectData Data; - QueuedEffectOperations.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); - QueuedEffectOperations.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ServerAuthMove); + + TGMASBoundQueueOperation Operation; + FGMCAbilityEffectData Data; + QueuedEffectOperations.MakeOperation(Operation, EGMASBoundQueueOperationType::Remove, FGameplayTag::EmptyTag, Data, Ids); + QueuedEffectOperations.QueuePreparedOperation(Operation, QueueType == EGMCAbilityEffectQueueType::ServerAuthMove); + + if (QueueType == EGMCAbilityEffectQueueType::ServerAuth) + { + // Send the operation over to our client via standard RPC. + ClientQueueEffectOperation(Operation); + } - if (QueueType == EGMCAbilityEffectQueueType::ServerAuth) - { - // Send the operation over to our client via standard RPC. - ClientQueueEffectOperation(Operation); + return true; } - } - - return true; } - + + ensureMsgf(false, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()); UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong!"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) return false; diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 992731f8..586e0d1a 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -53,17 +53,15 @@ void UGMCAbilityEffect::InitializeEffect(FGMCAbilityEffectData InitializationDat void UGMCAbilityEffect::StartEffect() { + bHasStarted = true; + // Ensure tag requirements are met before applying the effect - if( ( EffectData.ApplicationMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustNotHaveTags) || - ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) + if( ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) { EndEffect(); return; } - - bHasStarted = true; AddTagsToOwner(); AddAbilitiesToOwner(); @@ -165,7 +163,7 @@ void UGMCAbilityEffect::Tick(float DeltaTime) TickEvent(DeltaTime); // Ensure tag requirements are met before applying the effect - if( ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || + if( (EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) { EndEffect(); diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index b12c7511..4db546e4 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -114,8 +114,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo TMap GetActiveEffects() const { return ActiveEffects; } // Return active Effect with tag + // Match exact doesn't look for depth in the tag, it will only match the exact tag UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") - TArray GetActiveEffectsByTag(FGameplayTag GameplayTag) const; + TArray GetActiveEffectsByTag(FGameplayTag GameplayTag, bool bMatchExact = true) const; // Get the first active effect with the Effecttag UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") @@ -127,12 +128,6 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") void RemoveAbilityMapData(UGMCAbilityMapData* AbilityMapData); - UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") - void AddStartingEffects(TArray> EffectsToAdd); - - UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") - void RemoveStartingEffects(TArray> EffectsToRemove); - // Add an ability to the GrantedAbilities array UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") void GrantAbilityByTag(const FGameplayTag AbilityTag); @@ -184,7 +179,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void TryActivateAbilitiesByInputTag(const FGameplayTag& InputTag, const UInputAction* InputAction = nullptr, bool bFromMovementTick=true); // Do not call directly on client, go through QueueAbility. Can be used to call server-side abilities (like AI). - bool TryActivateAbility(TSubclassOf ActivatedAbility, const UInputAction* InputAction = nullptr); + bool TryActivateAbility(TSubclassOf ActivatedAbility, const UInputAction* InputAction = nullptr, const FGameplayTag ActivationTag = FGameplayTag::EmptyTag); // Queue an ability to be executed UFUNCTION(BlueprintCallable, DisplayName="Activate Ability", Category="GMAS|Abilities") @@ -631,9 +626,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void RemoveEffectHandle(int EffectHandle); - // doesn't work ATM. UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem", meta=(AllowPrivateAccess="true")) - bool bInGMCTime = false; + bool bInAncillaryTick = false; void ServerHandlePendingEffect(float DeltaTime); void ServerHandlePredictedPendingEffect(float DeltaTime); diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 340ee8d9..8bea4023 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -177,9 +177,9 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject FGMCAbilityEffectData EffectData; UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") - virtual void InitializeEffect(FGMCAbilityEffectData InitializationData); + void InitializeEffect(FGMCAbilityEffectData InitializationData); - virtual void EndEffect(); + void EndEffect(); virtual void BeginDestroy() override; @@ -205,7 +205,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Dynamic Condition"), Category="GMCAbilitySystem") bool AttributeDynamicCondition() const; - virtual void PeriodTick(); + void PeriodTick(); void UpdateState(EGMASEffectState State, bool Force=false); @@ -224,6 +224,14 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") UGMC_AbilitySystemComponent* OwnerAbilityComponent; +private: + bool bHasStarted; + + // Used for calculating when to tick Period effects + float PrevPeriodMod = 0; + + void CheckState(); + // Tags void AddTagsToOwner(); @@ -240,15 +248,10 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool DuplicateEffectAlreadyApplied(); // Apply the things that should happen as soon as an effect starts. Tags, instant effects, etc. - virtual void StartEffect(); + void StartEffect(); - bool bHasStarted; - -private: - // Used for calculating when to tick Period effects - float PrevPeriodMod = 0; - void CheckState(); + public: FString ToString() { From 2c5406ca4dd1d1c1702ee05faf5ac68906a7e50c Mon Sep 17 00:00:00 2001 From: Bean's Beans Studios Date: Sun, 23 Feb 2025 22:51:24 -0800 Subject: [PATCH 14/63] Display selected actor's GMAS data in debugger (#103) * Display selected actor's GMAS data in debugger * Add methods to add/remove starting effects --- .../Components/GMCAbilityComponent.cpp | 16 +++++++++ ...eplayDebuggerCategory_GMCAbilitySystem.cpp | 33 ++++++++----------- .../Public/Components/GMCAbilityComponent.h | 6 ++++ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index c369ba90..1e26cbbf 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -227,6 +227,22 @@ void UGMC_AbilitySystemComponent::RemoveAbilityMapData(UGMCAbilityMapData* Abili } } +void UGMC_AbilitySystemComponent::AddStartingEffects(TArray> EffectsToAdd) +{ + for (const TSubclassOf Effect : EffectsToAdd) + { + StartingEffects.AddUnique(Effect); + } +} + +void UGMC_AbilitySystemComponent::RemoveStartingEffects(TArray> EffectsToRemove) +{ + for (const TSubclassOf Effect : EffectsToRemove) + { + StartingEffects.Remove(Effect); + } +} + void UGMC_AbilitySystemComponent::GrantAbilityByTag(const FGameplayTag AbilityTag) { if (!GrantedAbilityTags.HasTagExact(AbilityTag)) diff --git a/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp b/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp index 63fb11dd..5d7b9cb0 100644 --- a/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp +++ b/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp @@ -15,25 +15,18 @@ FGameplayDebuggerCategory_GMCAbilitySystem::FGameplayDebuggerCategory_GMCAbility void FGameplayDebuggerCategory_GMCAbilitySystem::CollectData(APlayerController* OwnerPC, AActor* DebugActor) { - if (OwnerPC) + if (DebugActor) { - if (OwnerPC->GetPawn()) - { - DataPack.ActorName = OwnerPC->GetPawn()->GetName(); - - if (const UGMC_AbilitySystemComponent* AbilityComponent = OwnerPC->GetPawn()->FindComponentByClass()) - { - DataPack.GrantedAbilities = AbilityComponent->GetGrantedAbilities().ToStringSimple(); - DataPack.ActiveTags = AbilityComponent->GetActiveTags().ToStringSimple(); - DataPack.Attributes = AbilityComponent->GetAllAttributesString(); - DataPack.ActiveEffects = AbilityComponent->GetActiveEffectsString(); - DataPack.ActiveEffectData = AbilityComponent->GetActiveEffectsDataString(); - DataPack.ActiveAbilities = AbilityComponent->GetActiveAbilitiesString(); - } - } - else + DataPack.ActorName = DebugActor->GetName(); + + if (const UGMC_AbilitySystemComponent* AbilityComponent = DebugActor->FindComponentByClass()) { - DataPack.ActorName = TEXT("Spectator or missing pawn"); + DataPack.GrantedAbilities = AbilityComponent->GetGrantedAbilities().ToStringSimple(); + DataPack.ActiveTags = AbilityComponent->GetActiveTags().ToStringSimple(); + DataPack.Attributes = AbilityComponent->GetAllAttributesString(); + DataPack.ActiveEffects = AbilityComponent->GetActiveEffectsString(); + DataPack.ActiveEffectData = AbilityComponent->GetActiveEffectsDataString(); + DataPack.ActiveAbilities = AbilityComponent->GetActiveAbilitiesString(); } } } @@ -41,11 +34,13 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::CollectData(APlayerController* void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) { + const AActor* LocalDebugActor = FindLocalDebugActor(); + const UGMC_AbilitySystemComponent* AbilityComponent = LocalDebugActor ? LocalDebugActor->FindComponentByClass() : nullptr; + if (AbilityComponent == nullptr) return; + if (!DataPack.ActorName.IsEmpty()) { CanvasContext.Printf(TEXT("{yellow}Actor name: {white}%s"), *DataPack.ActorName); - const UGMC_AbilitySystemComponent* AbilityComponent = OwnerPC->GetPawn() ? OwnerPC->GetPawn()->FindComponentByClass() : nullptr; - if (AbilityComponent == nullptr) return; // Abilities CanvasContext.Printf(TEXT("{blue}[server] {yellow}Granted Abilities: {white}%s"), *DataPack.GrantedAbilities); diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 4db546e4..df0558f2 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -128,6 +128,12 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") void RemoveAbilityMapData(UGMCAbilityMapData* AbilityMapData); + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") + void AddStartingEffects(TArray> EffectsToAdd); + + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") + void RemoveStartingEffects(TArray> EffectsToRemove); + // Add an ability to the GrantedAbilities array UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") void GrantAbilityByTag(const FGameplayTag AbilityTag); From 84ea91af40279d7176526b3aec9788e9d9f14e09 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 24 Feb 2025 00:25:52 -0800 Subject: [PATCH 15/63] Server auth events 5.5 (#104) Server Auth Events! They're like server auth effects, but for events! * fix: merge conflict goofs * Remove unnecessary header includes * Keep StructUtils depdendency for older version support --- .../Components/GMCAbilityComponent.cpp | 197 +++++++++++++++--- .../Public/Components/GMCAbilityComponent.h | 53 ++++- .../Public/Utility/GMASSyncedEvent.h | 48 +++++ 3 files changed, 260 insertions(+), 38 deletions(-) create mode 100644 Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 1e26cbbf..7800c27f 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -13,6 +13,7 @@ #include "Kismet/KismetSystemLibrary.h" #include "Net/UnrealNetwork.h" + // Sets default values for this component's properties UGMC_AbilitySystemComponent::UGMC_AbilitySystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -150,6 +151,7 @@ void UGMC_AbilitySystemComponent::BindReplicationData() QueuedAbilityOperations.BindToGMC(GMCMovementComponent); QueuedEffectOperations.BindToGMC(GMCMovementComponent); QueuedEffectOperations_ClientAuth.BindToGMC(GMCMovementComponent); + QueuedEventOperations.BindToGMC(GMCMovementComponent); } void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsCombinedClientMove) @@ -159,8 +161,11 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb OnAncillaryTick.Broadcast(DeltaTime); - ClientHandlePendingEffect(); + ClientHandlePendingOperation(QueuedEffectOperations); ServerHandlePendingEffect(DeltaTime); + + ClientHandlePendingOperation(QueuedEventOperations); + CheckActiveTagsChanged(); CheckAttributeChanged(); @@ -546,6 +551,7 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) // Advance our queue action timers. QueuedAbilityOperations.GenPredictionTick(DeltaTime); QueuedEffectOperations.GenPredictionTick(DeltaTime); + QueuedEventOperations.GenPredictionTick(DeltaTime); // Was an ability used? if (TGMASBoundQueueOperation Operation; @@ -589,6 +595,7 @@ void UGMC_AbilitySystemComponent::PreLocalMoveExecution() { // We'll never get the pre remote movement trigger in this case, soooo... QueuedEffectOperations.PreRemoteMovement(); + QueuedEventOperations.PreRemoteMovement(); } } @@ -596,6 +603,7 @@ void UGMC_AbilitySystemComponent::PreRemoteMoveExecution() { // Advance our server-auth queues. QueuedEffectOperations.PreRemoteMovement(); + QueuedEventOperations.PreRemoteMovement(); } void UGMC_AbilitySystemComponent::BeginPlay() @@ -883,20 +891,20 @@ void UGMC_AbilitySystemComponent::ServerHandlePendingEffect(float DeltaTime) { QueuedEffectOperations.QueuePreparedOperation(BoundOperation, false); // And send it via RPC, so that the client gets it. - ClientQueueEffectOperation(BoundOperation); + ClientQueueOperation(BoundOperation); }; // Handle our 'outer' RPC effect operations. QueuedEffectOperations.DeductGracePeriod(DeltaTime); auto Operations = QueuedEffectOperations.GetQueuedRPCOperations(); for (auto& Operation : Operations) { - if (ShouldProcessEffectOperation(Operation, true)) + if (ShouldProcessOperation(Operation, QueuedEffectOperations, true)) { if (Operation.GracePeriodExpired()) { UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Client effect operation missed grace period, forcing on server.")) } - ProcessEffectOperation(Operation); + ProcessOperation(Operation); QueuedEffectOperations.RemoveOperationById(Operation.GetOperationId()); } } @@ -910,35 +918,35 @@ void UGMC_AbilitySystemComponent::ServerHandlePredictedPendingEffect(float Delta TGMASBoundQueueOperation BoundOperation; if (QueuedEffectOperations_ClientAuth.GetCurrentBoundOperation(BoundOperation, true)) { - ProcessEffectOperation(BoundOperation); + ProcessOperation(BoundOperation); } // Check for any queued-for-move predicted effects. // We use the client auth effect queue's (otherwise-unused) RPC operations queue to avoid creating an entire new one. while (QueuedEffectOperations_ClientAuth.PopNextRPCOperation(BoundOperation)) { - ProcessEffectOperation(BoundOperation); + ProcessOperation(BoundOperation); } } - -void UGMC_AbilitySystemComponent::ClientHandlePendingEffect() { +template +void UGMC_AbilitySystemComponent::ClientHandlePendingOperation(TGMASBoundQueue& QueuedOperations) { printf("ClientHandlePendingEffect"); // Handle our RPC effect operations. MoveCycle operations will be sent via RPC // just like the Outer ones, but will be preserved in the movement history. - auto RPCOperations = QueuedEffectOperations.GetQueuedRPCOperations(); + auto RPCOperations = QueuedOperations.GetQueuedRPCOperations(); for (auto& Operation : RPCOperations) { - if (QueuedEffectOperations.IsAcknowledged(Operation.GetOperationId())) + if (QueuedOperations.IsAcknowledged(Operation.GetOperationId())) { - ProcessEffectOperation(Operation); - QueuedEffectOperations.RemoveOperationById(Operation.GetOperationId()); + ProcessOperation(Operation); + QueuedOperations.RemoveOperationById(Operation.GetOperationId()); } - if (ShouldProcessEffectOperation(Operation, false)) + if (ShouldProcessOperation(Operation, QueuedOperations, false)) { - QueuedEffectOperations.Acknowledge(Operation.GetOperationId()); + QueuedOperations.Acknowledge(Operation.GetOperationId()); } } } @@ -948,13 +956,13 @@ void UGMC_AbilitySystemComponent::ClientHandlePredictedPendingEffect() TGMASBoundQueueOperation BoundOperation; if (QueuedEffectOperations_ClientAuth.GetCurrentBoundOperation(BoundOperation)) { - ProcessEffectOperation(BoundOperation); + ProcessOperation(BoundOperation); } // We use the client auth effect queue's (otherwise-unused) RPC operations queue to avoid creating an entire new one. while (QueuedEffectOperations_ClientAuth.PopNextRPCOperation(BoundOperation)) { - ProcessEffectOperation(BoundOperation); + ProcessOperation(BoundOperation); } } @@ -1156,7 +1164,7 @@ bool UGMC_AbilitySystemComponent::ProcessAbilityOperation( return false; } -UGMCAbilityEffect* UGMC_AbilitySystemComponent::ProcessEffectOperation( +UGMCAbilityEffect* UGMC_AbilitySystemComponent::ProcessOperation( const TGMASBoundQueueOperation& Operation) { EGMASBoundQueueOperationType OperationType = Operation.GetOperationType(); @@ -1221,37 +1229,147 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ProcessEffectOperation( return nullptr; } -bool UGMC_AbilitySystemComponent::ShouldProcessEffectOperation( - const TGMASBoundQueueOperation& Operation, bool bIsServer) const +void UGMC_AbilitySystemComponent::ExecuteSyncedEvent(FGMASSyncedEventContainer EventData) +{ + if (!HasAuthority()) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Client attempted to create a SyncedEvent")); + return; + } + + if (EventData.EventTag == FGameplayTag::EmptyTag) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Attempted to create a SyncedEvent with an empty tag")); + return; + } + + EventData.EventType = EGMASSyncedEventType::BlueprintImplemented; + + TGMASBoundQueueOperation Operation; + if (CreateSyncedEventOperation(Operation, EventData) == -1 ) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Failed to create SyncedEvent")); + return; + } + + QueuedEventOperations.QueuePreparedOperation(Operation, false); + ClientQueueOperation(Operation); +} + +void UGMC_AbilitySystemComponent::ProcessOperation(const TGMASBoundQueueOperation& Operation) +{ + FGMASSyncedEventContainer EventDataContainer = Operation.Payload; + + switch (EventDataContainer.EventType) + { + case EGMASSyncedEventType::BlueprintImplemented: + { + OnSyncedEvent.Broadcast(EventDataContainer); + break; + } + case EGMASSyncedEventType::AddImpulse: + { + AddImpulseEvent(EventDataContainer); + break; + } + case EGMASSyncedEventType::PlayMontage: + { + break; + } + default: + break; + } + +} + +template +bool UGMC_AbilitySystemComponent::IsOperationValid(const TGMASBoundQueueOperation& Operation) const { if (!Operation.IsValid()) { if (Operation.Header.OperationId != -1) { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%s] %s received invalid queued effect operation %d!"), + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%s] %s received invalid queued operation %d!"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Operation.Header.OperationId) return false; } return false; } - + return true; +} + +template +bool UGMC_AbilitySystemComponent::ShouldProcessOperation( + const TGMASBoundQueueOperation& Operation, TGMASBoundQueue& QueuedOperations, bool bIsServer) const +{ + if (!IsOperationValid(Operation)) + { + return false; + } + if (bIsServer) { - return HasAuthority() && (QueuedEffectOperations.IsAcknowledged(Operation.GetOperationId()) || + return HasAuthority() && (QueuedOperations.IsAcknowledged(Operation.GetOperationId()) || Operation.GracePeriodExpired() || GetNetMode() == NM_Standalone); } else { - return !QueuedEffectOperations.IsAcknowledged(Operation.GetOperationId()) && (GMCMovementComponent->IsLocallyControlledServerPawn() || GMCMovementComponent->IsAutonomousProxy()); + return !QueuedOperations.IsAcknowledged(Operation.GetOperationId()) && (GMCMovementComponent->IsLocallyControlledServerPawn() || GMCMovementComponent->IsAutonomousProxy()); + } +} + +void UGMC_AbilitySystemComponent::AddImpulse(FVector Impulse, bool bVelChange) +{ + if (!HasAuthority()) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Client attempted to apply knock back effect")); + return; + } + + TGMASBoundQueueOperation Operation; + FGMASSyncedEventContainer EventData; + + FGMASSyncedEventData_AddImpulse ImpulseData; + ImpulseData.Impulse = Impulse; + ImpulseData.bVelocityChange = bVelChange; + + EventData.EventType = EGMASSyncedEventType::AddImpulse; + EventData.InstancedPayload = FInstancedStruct::Make(ImpulseData); + + if (CreateSyncedEventOperation(Operation, EventData) == -1 ) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Failed to create knock back effect")); + return; } + + QueuedEventOperations.QueuePreparedOperation(Operation, false); + ClientQueueOperation(Operation); + } -void UGMC_AbilitySystemComponent::ClientQueueEffectOperation( +void UGMC_AbilitySystemComponent::AddImpulseEvent(const FGMASSyncedEventContainer& EventData) const +{ + const FGMASSyncedEventData_AddImpulse KBData = EventData.InstancedPayload.Get(); + GMCMovementComponent->AddImpulse(KBData.Impulse, KBData.bVelocityChange); +} + +void UGMC_AbilitySystemComponent::ClientQueueOperation( const TGMASBoundQueueOperation& Operation) { RPCClientQueueEffectOperation(Operation.Header); } +void UGMC_AbilitySystemComponent::ClientQueueOperation( + const TGMASBoundQueueOperation& Operation) +{ + RPCClientQueueEventOperation(Operation.Header); +} + +void UGMC_AbilitySystemComponent::RPCClientQueueEventOperation_Implementation(const FGMASBoundQueueRPCHeader& Header) +{ + QueuedEventOperations.QueueOperationFromHeader(Header, false); +} + void UGMC_AbilitySystemComponent::RPCClientQueueEffectOperation_Implementation(const FGMASBoundQueueRPCHeader& Header) { QueuedEffectOperations.QueueOperationFromHeader(Header, false); @@ -1371,6 +1489,24 @@ int UGMC_AbilitySystemComponent::CreateEffectOperation( return PayloadData.EffectID; } +int UGMC_AbilitySystemComponent::CreateSyncedEventOperation( + TGMASBoundQueueOperation& OutOperation, + const FGMASSyncedEventContainer& EventData) +{ + if (!HasAuthority()) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Client attempted to create a synced event operation")); + return -1; + } + + TArray PayloadIds {}; + int ID = GetNextAvailableEffectID(); + PayloadIds.Add(ID); + QueuedEventOperations.MakeOperation(OutOperation, EGMASBoundQueueOperationType::Add, FGameplayTag::EmptyTag, EventData, PayloadIds, nullptr, 1.f, static_cast(EGMCAbilityEffectQueueType::ServerAuth)); + + return ID; +} + //BP Version UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Effect, FGMCAbilityEffectData InitializationData, bool bOuterActivation) { @@ -1392,7 +1528,7 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfGetName(), *Effect->GetName()) } - return ProcessEffectOperation(Operation); + return ProcessOperation(Operation); } int32 UGMC_AbilitySystemComponent::GetNextAvailableEffectHandle() const @@ -1511,7 +1647,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfEffectData.EffectID; OutEffectHandle = HandleData.Handle; return true; @@ -1521,7 +1657,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfIsExecutingMove() || bInAncillaryTick) { // We're in a move context, just add it directly rather than queuing. - OutEffect = ProcessEffectOperation(Operation); + OutEffect = ProcessOperation(Operation); OutEffectId = OutEffect->EffectData.EffectID; } else @@ -1549,7 +1685,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Ids, EGMCAbil if (QueueType == EGMCAbilityEffectQueueType::ServerAuth) { // Send the operation over to our client via standard RPC. - ClientQueueEffectOperation(Operation); + ClientQueueOperation(Operation); } - return true; } } diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index df0558f2..84dc1bb5 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -12,6 +12,7 @@ #include "Effects/GMCAbilityEffect.h" #include "Components/ActorComponent.h" #include "Utility/GMASBoundQueue.h" +#include "Utility/GMASSyncedEvent.h" #include "GMCAbilityComponent.generated.h" @@ -25,6 +26,8 @@ DECLARE_MULTICAST_DELEGATE_ThreeParams(FGameplayAttributeChangedNative, const FG DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAncillaryTick, float, DeltaTime); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSyncedEvent, const FGMASSyncedEventContainer&, EventData); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActiveTagsChanged, FGameplayTagContainer, AddedTags, FGameplayTagContainer, RemovedTags); DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, const FGameplayTagContainer&, const FGameplayTagContainer&); @@ -262,6 +265,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo int GetNextAvailableEffectID() const; bool CheckIfEffectIDQueued(int EffectID) const; int CreateEffectOperation(TGMASBoundQueueOperation& OutOperation, const TSubclassOf& Effect, const FGMCAbilityEffectData& EffectData, bool bForcedEffectId = true, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + int CreateSyncedEventOperation(TGMASBoundQueueOperation& OutOperation, const FGMASSyncedEventContainer& EventData); + /** * Applies an effect to the Ability Component. If bOuterActivation is false, the effect will be immediately @@ -373,6 +378,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY(BlueprintAssignable) FOnActiveTagsChanged OnActiveTagsChanged; + // Called when a synced event is executed + UPROPERTY(BlueprintAssignable) + FOnSyncedEvent OnSyncedEvent; + FGameplayTagContainer PreviousActiveTags; /** Returns an array of pointers to all attributes */ @@ -444,6 +453,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo */ void RemoveAttributeChangeDelegate(FDelegateHandle Handle); + #pragma region GMC // GMC UFUNCTION(BlueprintCallable, Category="GMAS") @@ -546,26 +556,53 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo TGMASBoundQueue QueuedEffectOperations; TGMASBoundQueue QueuedEffectOperations_ClientAuth; - UGMCAbilityEffect* ProcessEffectOperation(const TGMASBoundQueueOperation& Operation); - bool ShouldProcessEffectOperation(const TGMASBoundQueueOperation& Operation, bool bIsServer = true) const; - void ClientQueueEffectOperation(const TGMASBoundQueueOperation& Operation); + TGMASBoundQueue QueuedEventOperations; + + + template + bool IsOperationValid(const TGMASBoundQueueOperation& Operation) const; + + template + bool ShouldProcessOperation(const TGMASBoundQueueOperation& Operation, TGMASBoundQueue& QueuedOperations, bool bIsServer = true) const; + + // Events + virtual void ProcessOperation(const TGMASBoundQueueOperation& Operation); + + // Event Implementations + + // Execute an event that is created by the server where execution is synced between server and client + UFUNCTION(BlueprintCallable) + void ExecuteSyncedEvent(FGMASSyncedEventContainer EventData); + + + + UFUNCTION(BlueprintCallable, DisplayName="Add Impulse (Synced Event)") + void AddImpulse(FVector Impulse, bool bVelChange = false); + void AddImpulseEvent(const FGMASSyncedEventContainer& EventData) const; + + // Effects + virtual UGMCAbilityEffect* ProcessOperation(const TGMASBoundQueueOperation& Operation); + + + void ClientQueueOperation(const TGMASBoundQueueOperation& Operation); + void ClientQueueOperation(const TGMASBoundQueueOperation& Operation); UFUNCTION(Client, Reliable) void RPCClientQueueEffectOperation(const FGMASBoundQueueRPCHeader& Header); + + UFUNCTION(Client, Reliable) + void RPCClientQueueEventOperation(const FGMASBoundQueueRPCHeader& Header); // Predictions of Effect state changes FEffectStatePrediction EffectStatePrediction{}; TArray QueuedEffectStates; - - UPROPERTY() TMap ActiveAbilities; - UPROPERTY() TMap ActiveCooldowns; @@ -638,7 +675,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void ServerHandlePendingEffect(float DeltaTime); void ServerHandlePredictedPendingEffect(float DeltaTime); - void ClientHandlePendingEffect(); + template + void ClientHandlePendingOperation(TGMASBoundQueue& QueuedOperations); + void ClientHandlePredictedPendingEffect(); int LateApplicationIDCounter = 0; diff --git a/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h b/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h new file mode 100644 index 00000000..c8a8c255 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTags.h" +#include "StructUtils/InstancedStruct.h" +#include "GMASSyncedEvent.generated.h" + +UCLASS() +class GMCABILITYSYSTEM_API UGMASSyncedEvent : public UObject +{ + GENERATED_BODY() +}; + +UENUM() +enum EGMASSyncedEventType +{ + BlueprintImplemented, + Custom, + AddImpulse, + PlayMontage +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FGMASSyncedEventContainer +{ + GENERATED_BODY() + + UPROPERTY() + TEnumAsByte EventType{BlueprintImplemented}; + + UPROPERTY(BlueprintReadWrite) + FGameplayTag EventTag; + + UPROPERTY(BlueprintReadWrite) + FInstancedStruct InstancedPayload; +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FGMASSyncedEventData_AddImpulse +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite) + FVector Impulse {FVector::Zero()}; + + UPROPERTY(BlueprintReadWrite) + bool bVelocityChange {false}; +}; \ No newline at end of file From 930004a7db185c4bb82c718dd57e7334ba898da5 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Tue, 7 Jan 2025 22:25:36 +0100 Subject: [PATCH 16/63] Fixing issue, fix crash on revive, adding --- Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 84dc1bb5..9dda0b66 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -390,6 +390,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo /** Get an Attribute using its Tag */ const FAttribute* GetAttributeByTag(FGameplayTag AttributeTag) const; + TMap GetActiveAbilities() const { return ActiveAbilities; } + // Get Attribute value by Tag UFUNCTION(BlueprintPure, Category="GMAS|Attributes") float GetAttributeValueByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; From 9651598be9506fc002f690bfcd7b28dd582d1545 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 23 Jan 2025 13:14:55 +0100 Subject: [PATCH 17/63] Add CancelAbilityOnEnd to effects, allowing to stop abilities when the effect End. --- .../Private/Effects/GMCAbilityEffect.cpp | 9 +++++---- .../GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 586e0d1a..b0c7f777 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -65,7 +65,7 @@ void UGMCAbilityEffect::StartEffect() AddTagsToOwner(); AddAbilitiesToOwner(); - EndActiveAbilitiesFromOwner(); + EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnActivation); // Instant effects modify base value and end instantly if (EffectData.bIsInstant) @@ -125,7 +125,8 @@ void UGMCAbilityEffect::EndEffect() OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, false, true); } } - + + EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnEnd); RemoveTagsFromOwner(EffectData.bPreserveGrantedTagsIfMultiple); RemoveAbilitiesFromOwner(); } @@ -269,9 +270,9 @@ void UGMCAbilityEffect::RemoveAbilitiesFromOwner() } -void UGMCAbilityEffect::EndActiveAbilitiesFromOwner() { +void UGMCAbilityEffect::EndActiveAbilitiesFromOwner(const FGameplayTagContainer& TagContainer) { - for (const FGameplayTag Tag : EffectData.CancelAbilityOnActivation) + for (const FGameplayTag Tag : TagContainer) { OwnerAbilityComponent->EndAbilitiesByTag(Tag); } diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 8bea4023..ebeeb2b1 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -140,6 +140,10 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer CancelAbilityOnActivation; + // When this effect end, it will end ability present in this container + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + FGameplayTagContainer CancelAbilityOnEnd; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") TArray Modifiers; @@ -240,7 +244,8 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject void AddAbilitiesToOwner(); void RemoveAbilitiesFromOwner(); - void EndActiveAbilitiesFromOwner(); + void EndActiveAbilitiesFromOwner(const FGameplayTagContainer& TagContainer); + // Does the owner have any of the tags from the container? bool DoesOwnerHaveTagFromContainer(FGameplayTagContainer& TagContainer) const; From 65b3e7213c3b3092653a495f977ed65c0f579898 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 23 Jan 2025 13:26:50 +0100 Subject: [PATCH 18/63] Fixing : Ability blocked by tag doesn't respect hierarchy. e.g : Before -> Ability.Item as blocked tag would not block Ability.Item.Use, Ability.Item.Consume but only Ability.Item Now -> Ability.Item as blocked tag would block Ability.Item.Use, Ability.Item.Consume and Ability.Item --- .../GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 7800c27f..c7d06842 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -417,7 +417,7 @@ bool UGMC_AbilitySystemComponent::IsAbilityTagBlocked(const FGameplayTag Ability if (IsValid(ActiveAbility.Value) && ActiveAbility.Value->AbilityState != EAbilityState::Ended) { for (auto& Tag : ActiveAbility.Value->BlockOtherAbility) { - if (Tag.MatchesTag(AbilityTag)) { + if (AbilityTag.MatchesTag(Tag)) { UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability can't activate, blocked by Ability: %s"), *ActiveAbility.Value->GetName()); return true; } From 474d01784900f83e949d96b3e57d8cd4424fad3d Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 23 Jan 2025 13:44:09 +0100 Subject: [PATCH 19/63] Adding short wrapper of out ability effect, for c++ convenience. --- .../Private/Components/GMCAbilityComponent.cpp | 15 ++++++++++++++- .../Public/Components/GMCAbilityComponent.h | 5 +++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index c7d06842..898f23c1 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -1606,8 +1606,21 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectSafe(TSubclassOf EffectClass, EGMCAbilityEffectQueueType QueueType) { + + bool bOutSuccess; + int OutEffectHandle; + int OutEffectId; + UGMCAbilityEffect* OutEffect = nullptr; + + ApplyAbilityEffectSafe(EffectClass, FGMCAbilityEffectData{}, QueueType, bOutSuccess, OutEffectHandle, OutEffectId, OutEffect); + return OutEffect; +} + + bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf EffectClass, - FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectHandle, int& OutEffectId, UGMCAbilityEffect*& OutEffect) + FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectHandle, int& OutEffectId, UGMCAbilityEffect*& OutEffect) { OutEffect = nullptr; OutEffectId = -1; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 9dda0b66..61b83331 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -291,6 +291,11 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void ApplyAbilityEffectSafe(TSubclassOf EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, UPARAM(DisplayName="Success") bool& OutSuccess, UPARAM(DisplayName="Effect Handle") int& OutEffectHandle, UPARAM(DisplayName="Effect Network ID") int& OutEffectId, UPARAM(DisplayName="Effect Instance") UGMCAbilityEffect*& OutEffect); + /** Short version of ApplyAbilityEffect (Fire and Forget, return nullptr if fail, or the effect instance if success) + * Don't suggest it for BP user to avoid confusion. + */ + UGMCAbilityEffect* ApplyAbilityEffectShort(TSubclassOf EffectClass, EGMCAbilityEffectQueueType QueueType); + /** * Applies an effect to the ability component. If the Queue Type is Predicted, the effect will be immediately added * on both client and server; this must happen within the GMC movement lifecycle for it to be valid. If the From 827def864e9f88103501e91dd1ce56b901c9c571 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 23 Jan 2025 13:49:44 +0100 Subject: [PATCH 20/63] Adding Remove ability by tag --- .../Components/GMCAbilityComponent.cpp | 19 ++++++++++++++++++- .../Public/Components/GMCAbilityComponent.h | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 898f23c1..94483d34 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -1615,7 +1615,7 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffectShort(TSubclas UGMCAbilityEffect* OutEffect = nullptr; ApplyAbilityEffectSafe(EffectClass, FGMCAbilityEffectData{}, QueueType, bOutSuccess, OutEffectHandle, OutEffectId, OutEffect); - return OutEffect; + return bOutSuccess ? OutEffect : nullptr; } @@ -1786,6 +1786,23 @@ void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffectSafe(UGMCAbilityEffec RemoveEffectByIdSafe({ Effect->EffectData.EffectID }, QueueType); } + +void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffectByTag(FGameplayTag Tag, EGMCAbilityEffectQueueType QueueType, bool bAllInstance) { + + for (auto& [EffectId, Effect] : ActiveEffects) + { + if (IsValid(Effect) && Tag.MatchesTag(Effect->EffectData.EffectTag)) + { + RemoveEffectByIdSafe({ EffectId }, QueueType); + if (!bAllInstance) { + return; + } + } + } + +} + + TArray UGMC_AbilitySystemComponent::EffectsMatchingTag(const FGameplayTag& Tag, int32 NumToRemove) const { if (NumToRemove < -1 || !Tag.IsValid()) { diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 61b83331..47a3fbde 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -326,6 +326,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Active Ability Effect (Safe)") void RemoveActiveAbilityEffectSafe(UGMCAbilityEffect* Effect, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Active Ability Effect by Tag (Safe)") + void RemoveActiveAbilityEffectByTag(FGameplayTag Tag, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted, bool bAllInstance = false); /** * Removes an instanced effect if it exists. If NumToRemove == -1, remove all. Returns the number of removed instances. From 3899a388d3613137198454e2e136ac794c5e5794 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Sat, 25 Jan 2025 13:12:32 +0100 Subject: [PATCH 21/63] Unreachable attempt to catch. Try to understand why, how, etc etc. --- .../Private/Effects/GMCAbilityEffect.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index b0c7f777..3f4f9d94 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -159,7 +159,15 @@ void UGMCAbilityEffect::BeginDestroy() { void UGMCAbilityEffect::Tick(float DeltaTime) { - if (bCompleted) return; + // Aherys : I'm not sure if this is correct. Sometime this is GC. We need to catch why, and when. + if (bCompleted || IsUnreachable()) { + if (IsUnreachable()) { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect is unreachable : %s"), *EffectData.EffectTag.ToString()); + ensureMsgf(false, TEXT("Effect is being ticked after being completed or GC : %s"), *EffectData.EffectTag.ToString()); + } + return; + } + EffectData.CurrentDuration += DeltaTime; TickEvent(DeltaTime); From bd17aeafe4dd4942a328870fc40aa4015772a4dc Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Wed, 29 Jan 2025 14:20:46 +0100 Subject: [PATCH 22/63] Adding Starting effect and Ending effect event for blueprint. --- .../Private/Effects/GMCAbilityEffect.cpp | 6 +++++- .../Public/Effects/GMCAbilityEffect.h | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 3f4f9d94..21e66f29 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -100,6 +100,8 @@ void UGMCAbilityEffect::StartEffect() EndEffect(); } + StartEffectEvent(); + UpdateState(EGMASEffectState::Started, true); } @@ -125,10 +127,12 @@ void UGMCAbilityEffect::EndEffect() OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, false, true); } } - + EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnEnd); RemoveTagsFromOwner(EffectData.bPreserveGrantedTagsIfMultiple); RemoveAbilitiesFromOwner(); + + EndEffectEvent(); } diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index ebeeb2b1..b793caf6 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -256,9 +256,16 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject void StartEffect(); - - public: + + // Blueprint Event for when the effect starts + UFUNCTION(BlueprintImplementableEvent) + void StartEffectEvent(); + + UFUNCTION(BlueprintImplementableEvent) + void EndEffectEvent(); + + FString ToString() { return FString::Printf(TEXT("[name: %s] (State %s) | Started: %d | Period Paused: %d | Data: %s"), *GetName(), *EnumToString(CurrentState), bHasStarted, IsPeriodPaused(), *EffectData.ToString()); } From fcfddc798c100afb72a6455940325d24d7901610 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 30 Jan 2025 13:51:09 +0100 Subject: [PATCH 23/63] Virtualised Start Effect. --- Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index b793caf6..398d98bc 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -253,7 +253,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool DuplicateEffectAlreadyApplied(); // Apply the things that should happen as soon as an effect starts. Tags, instant effects, etc. - void StartEffect(); + virtual void StartEffect(); public: From ccb9e93185d3c36f125a44ab73a3f8a1e57f7dad Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 30 Jan 2025 13:59:37 +0100 Subject: [PATCH 24/63] Fixing bad class accessibility --- .../GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 398d98bc..50539f10 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -223,10 +223,13 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject protected: UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") - UGMC_AbilitySystemComponent* SourceAbilityComponent; + UGMC_AbilitySystemComponent* SourceAbilityComponent = nullptr; UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") - UGMC_AbilitySystemComponent* OwnerAbilityComponent; + UGMC_AbilitySystemComponent* OwnerAbilityComponent = nullptr; + + // Apply the things that should happen as soon as an effect starts. Tags, instant effects, etc. + virtual void StartEffect(); private: bool bHasStarted; @@ -252,8 +255,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool DuplicateEffectAlreadyApplied(); - // Apply the things that should happen as soon as an effect starts. Tags, instant effects, etc. - virtual void StartEffect(); + public: From c32c0509f439a002db5601fdbb95d93b93411fc7 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Sat, 15 Feb 2025 15:51:43 +0100 Subject: [PATCH 25/63] Fix log spamming (thank me later blue) --- .../GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 94483d34..ee4924f2 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -933,8 +933,6 @@ void UGMC_AbilitySystemComponent::ServerHandlePredictedPendingEffect(float Delta template void UGMC_AbilitySystemComponent::ClientHandlePendingOperation(TGMASBoundQueue& QueuedOperations) { - printf("ClientHandlePendingEffect"); - // Handle our RPC effect operations. MoveCycle operations will be sent via RPC // just like the Outer ones, but will be preserved in the movement history. auto RPCOperations = QueuedOperations.GetQueuedRPCOperations(); From aed29907abdedd9465036ae1e26981cf23cd3083 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 20 Feb 2025 11:55:39 +0100 Subject: [PATCH 26/63] Add better debug message for miss queue prediction of an effect (now display the list of concerned effect) --- .../Components/GMCAbilityComponent.cpp | 59 ++++++++++++++----- .../Public/Components/GMCAbilityComponent.h | 6 ++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index ee4924f2..cf9af2dc 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -1731,6 +1731,32 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::GetEffectById(const int EffectId return ActiveEffects[EffectId]; } + +TArray UGMC_AbilitySystemComponent::GetEffectsByIds(const TArray EffectIds) const { + TArray EffectsFound; + + for (int EffectId : EffectIds) + { + if (ActiveEffects.Contains(EffectId)) + { + EffectsFound.Add(ActiveEffects[EffectId]); + } + } + + return EffectsFound; +} + + +FString UGMC_AbilitySystemComponent::GetEffectsNameAsString(const TArray& EffectList) const { + FString EffectNames; + for (UGMCAbilityEffect* Effect : EffectList) + { + EffectNames += (Effect ? GetNameSafe(Effect->GetClass()) : "NULLPTR EFFECT") + ", "; + } + return EffectNames; +} + + UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(UGMCAbilityEffect* Effect, FGMCAbilityEffectData InitializationData) { if (Effect == nullptr) { @@ -1879,10 +1905,11 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil { if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone && !bInAncillaryTick) { - ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()); - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) + + ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); return false; } @@ -1920,10 +1947,10 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil { if (GetNetMode() != NM_Standalone && (HasAuthority() && !GMCMovementComponent->IsLocallyControlledServerPawn())) { - ensureMsgf(false, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()); - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) + ensureMsgf(false, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a client-auth removal of %d effects on a server! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num(), *GetEffectsNameAsString(GetEffectsByIds(Ids))) return false; } } @@ -1939,10 +1966,10 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil { if (!HasAuthority()) { - ensureMsgf(false, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()); - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num()) + ensureMsgf(false, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a server-auth removal of %d effects on a client! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), Ids.Num(), *GetEffectsNameAsString(GetEffectsByIds(Ids))) return false; } @@ -1960,10 +1987,10 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil } } - ensureMsgf(false, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()); - UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong!"), - *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()) + ensureMsgf(false, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); + UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a removal of effects but something went horribly wrong! (%s)"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(),*GetEffectsNameAsString(GetEffectsByIds(Ids))) return false; } diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 47a3fbde..7d2ec956 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -317,6 +317,12 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Effects") UGMCAbilityEffect* GetEffectById(const int EffectId) const; + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + TArray GetEffectsByIds(const TArray EffectIds) const; + + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + FString GetEffectsNameAsString(const TArray& EffectList) const; + TArray EffectsMatchingTag(const FGameplayTag& Tag, int32 NumToRemove = -1) const; // Do not call this directly unless you know what you are doing; go through the RemoveActiveAbilityEffectSafe if From 95596d6084b12cc701ad0e2ac5d3342ed1f41b6f Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Mon, 24 Feb 2025 11:56:16 +0100 Subject: [PATCH 27/63] Fixing missing category specifier. --- .../Public/Components/GMCAbilityComponent.h | 4 ++-- Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 7d2ec956..daec399a 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -588,12 +588,12 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Event Implementations // Execute an event that is created by the server where execution is synced between server and client - UFUNCTION(BlueprintCallable) + UFUNCTION(BlueprintCallable, Category = "GMASSyncedEvent") void ExecuteSyncedEvent(FGMASSyncedEventContainer EventData); - UFUNCTION(BlueprintCallable, DisplayName="Add Impulse (Synced Event)") + UFUNCTION(BlueprintCallable, DisplayName="Add Impulse (Synced Event)", Category = "Impulse") void AddImpulse(FVector Impulse, bool bVelChange = false); void AddImpulseEvent(const FGMASSyncedEventContainer& EventData) const; diff --git a/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h b/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h index c8a8c255..8841d8cc 100644 --- a/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h +++ b/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h @@ -28,10 +28,10 @@ struct GMCABILITYSYSTEM_API FGMASSyncedEventContainer UPROPERTY() TEnumAsByte EventType{BlueprintImplemented}; - UPROPERTY(BlueprintReadWrite) + UPROPERTY(BlueprintReadWrite, Category = "GMASSyncedEvent") FGameplayTag EventTag; - UPROPERTY(BlueprintReadWrite) + UPROPERTY(BlueprintReadWrite, Category = "GMASSyncedEvent") FInstancedStruct InstancedPayload; }; @@ -40,9 +40,9 @@ struct GMCABILITYSYSTEM_API FGMASSyncedEventData_AddImpulse { GENERATED_BODY() - UPROPERTY(BlueprintReadWrite) + UPROPERTY(BlueprintReadWrite, Category = "Impulse") FVector Impulse {FVector::Zero()}; - UPROPERTY(BlueprintReadWrite) + UPROPERTY(BlueprintReadWrite, Category = "Impulse") bool bVelocityChange {false}; }; \ No newline at end of file From d30a71e752beaf5f14e5a09079f1d57208e80658 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Mon, 24 Feb 2025 12:16:46 +0100 Subject: [PATCH 28/63] Fixing version compability with 5.4 and 5.5 for instanced struct --- Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h b/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h index 8841d8cc..f34d1155 100644 --- a/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h +++ b/Source/GMCAbilitySystem/Public/Utility/GMASSyncedEvent.h @@ -2,7 +2,14 @@ #include "CoreMinimal.h" #include "GameplayTags.h" -#include "StructUtils/InstancedStruct.h" + +// Fix for instanced struct on previous 5.5 version +#if ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION >= 5 + #include "StructUtils/InstancedStruct.h" +#else + #include "InstancedStruct.h" +#endif + #include "GMASSyncedEvent.generated.h" UCLASS() From 763f3404ed50859d60375d57091af5a99a2cc11a Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 24 Feb 2025 03:48:54 -0800 Subject: [PATCH 29/63] Remove outdated GMCAbilityOuterApplication.h --- .../Components/GMCAbilityOuterApplication.h | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h deleted file mode 100644 index 85bffac5..00000000 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityOuterApplication.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "InstancedStruct.h" -#include "GMCAbilityOuterApplication.generated.h" - -USTRUCT() -struct FGMCAcknowledgeId { - GENERATED_BODY() - - UPROPERTY() - TArray Id = {}; -}; - - -UENUM() -enum EGMCOuterApplicationType { - EGMC_AddEffect, - EGMC_RemoveEffect, -}; - -USTRUCT(BlueprintType) -struct FGMCOuterEffectAdd { - GENERATED_BODY() - - UPROPERTY() - TSubclassOf EffectClass; - - UPROPERTY() - FGMCAbilityEffectData InitializationData; -}; - -USTRUCT(BlueprintType) -struct FGMCOuterEffectRemove { - GENERATED_BODY() - - UPROPERTY() - TArray Ids = {}; -}; - -USTRUCT(BlueprintType) -struct FGMCOuterApplicationWrapper { - GENERATED_BODY() - - UPROPERTY() - TEnumAsByte Type = EGMC_AddEffect; - - UPROPERTY() - FInstancedStruct OuterApplicationData; - - UPROPERTY() - int LateApplicationID = 0; - - float ClientGraceTimeRemaining = 0.f; - - template - static FGMCOuterApplicationWrapper Make(Args... args) - { - FGMCOuterApplicationWrapper Wrapper; - Wrapper.OuterApplicationData = FInstancedStruct::Make(args...); - return Wrapper; - } - - -}; - -template<> inline FGMCOuterApplicationWrapper FGMCOuterApplicationWrapper::Make(TSubclassOf Effect, FGMCAbilityEffectData InitializationData) -{ - FGMCOuterApplicationWrapper Wrapper; - Wrapper.Type = EGMC_AddEffect; - Wrapper.OuterApplicationData = FInstancedStruct::Make(); - FGMCOuterEffectAdd& Data = Wrapper.OuterApplicationData.GetMutable(); - Data.EffectClass = Effect; - Data.InitializationData = InitializationData; - return Wrapper; -} - -template<> inline FGMCOuterApplicationWrapper FGMCOuterApplicationWrapper::Make(TArray Ids) -{ - FGMCOuterApplicationWrapper Wrapper; - Wrapper.Type = EGMC_RemoveEffect; - Wrapper.OuterApplicationData = FInstancedStruct::Make(); - FGMCOuterEffectRemove& Data = Wrapper.OuterApplicationData.GetMutable(); - Data.Ids = Ids; - return Wrapper; -} From 8cd1f13865820932c6e3cf8dafdddab479e3426f Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 24 Feb 2025 04:00:57 -0800 Subject: [PATCH 30/63] Refactor deprecated RemoveAtSwap for UE5.4+ --- .../Private/Utility/GameplayElementMapping.cpp | 6 +++++- Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp b/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp index eb72749c..a05ac1e4 100644 --- a/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp +++ b/Source/GMCAbilitySystem/Private/Utility/GameplayElementMapping.cpp @@ -148,7 +148,11 @@ void FGMCGameplayElementTagPropertyMap::Initialize(UObject* Owner, UGMC_AbilityS // We're invalid, so remove. UE_LOG(LogGMCAbilitySystem, Error, TEXT("FGMCGameplayElementTagPropertyMap: Removing invalid mapping [index %d, tags %s, property %s] for %s"), Index, *Mapping.TagsToMap.ToString(), *Mapping.PropertyName.ToString(), *GetNameSafe(Owner)); - PropertyMappings.RemoveAtSwap(Index, 1, false); +#if ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION >= 4 + PropertyMappings.RemoveAtSwap(Index, 1, EAllowShrinking::No); +#else + PropertyMappings.RemoveAtSwap(Index, 1, false); +#endif } ApplyCurrentTags(); diff --git a/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h b/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h index fb91525a..766133a8 100644 --- a/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h +++ b/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h @@ -483,7 +483,11 @@ class GMCABILITYSYSTEM_API TGMASBoundQueue if (TargetIdx != -1) { +#if ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION >= 4 + QueuedRPCOperations.RemoveAtSwap(TargetIdx, 1, EAllowShrinking::No); +#else QueuedRPCOperations.RemoveAtSwap(TargetIdx, 1, false); +#endif return true; } @@ -498,7 +502,11 @@ class GMCABILITYSYSTEM_API TGMASBoundQueue if (TargetIdx != -1) { +#if ENGINE_MAJOR_VERSION >=5 && ENGINE_MINOR_VERSION >= 4 + QueuedBoundOperations.RemoveAtSwap(TargetIdx, 1, EAllowShrinking::No); +#else QueuedBoundOperations.RemoveAtSwap(TargetIdx, 1, false); +#endif return true; } From 1619bc5acc8faa0a51153fac8e3477e53ef4f8c7 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 24 Feb 2025 04:14:57 -0800 Subject: [PATCH 31/63] Update Plugin details --- GMCAbilitySystem.uplugin | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GMCAbilitySystem.uplugin b/GMCAbilitySystem.uplugin index dfcbf655..c6b7528a 100644 --- a/GMCAbilitySystem.uplugin +++ b/GMCAbilitySystem.uplugin @@ -1,15 +1,15 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "1.0", - "FriendlyName": "GMCAbilitySystem", - "Description": "", - "Category": "GMC", + "VersionName": "1.3", + "FriendlyName": "GMAS - GMC Ability System", + "Description": "An ability system for GMC (General Movement Component)", + "Category": "GMAS", "CreatedBy": "Reznok", "CreatedByURL": "", "DocsURL": "", "MarketplaceURL": "", - "SupportURL": "", + "SupportURL": "https://github.com/reznok/GMCAbilitySystem", "CanContainContent": true, "IsBetaVersion": false, "IsExperimentalVersion": false, From 0b2db59ea8a49c04f405f2262dbb6e048da18d4e Mon Sep 17 00:00:00 2001 From: Aherys Date: Tue, 25 Feb 2025 19:40:44 +0100 Subject: [PATCH 32/63] - Fix for linux compile on GMC --- .../Private/Components/GMCAbilityComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index cf9af2dc..3e8c36e6 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -234,7 +234,7 @@ void UGMC_AbilitySystemComponent::RemoveAbilityMapData(UGMCAbilityMapData* Abili void UGMC_AbilitySystemComponent::AddStartingEffects(TArray> EffectsToAdd) { - for (const TSubclassOf Effect : EffectsToAdd) + for (const TSubclassOf& Effect : EffectsToAdd) { StartingEffects.AddUnique(Effect); } @@ -242,7 +242,7 @@ void UGMC_AbilitySystemComponent::AddStartingEffects(TArray> EffectsToRemove) { - for (const TSubclassOf Effect : EffectsToRemove) + for (const TSubclassOf& Effect : EffectsToRemove) { StartingEffects.Remove(Effect); } From fb5b73b4a7c317e7c11e662d837e873ce386bfe7 Mon Sep 17 00:00:00 2001 From: Nas <37818222+Nasuru@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:46:57 -0700 Subject: [PATCH 33/63] Add getter for ability map and blueprint read GetActiveTags (#109) --- .../GMCAbilitySystem/Public/Components/GMCAbilityComponent.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index daec399a..d4e1330a 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -111,6 +111,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo FGameplayTagContainer GetGrantedAbilities() const { return GrantedAbilityTags; } // Gameplay tags that the controller has + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") FGameplayTagContainer GetActiveTags() const { return ActiveTags; } // Return the active ability effects @@ -125,6 +126,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") UGMCAbilityEffect* GetFirstActiveEffectByTag(FGameplayTag GameplayTag) const; + // Return ability map that contains mapping of ability input tags to ability classes + TMap GetAbilityMap() { return AbilityMap; } + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") void AddAbilityMapData(UGMCAbilityMapData* AbilityMapData); From e5b8265cd95e6646dae2b20c14dfd624fbb7f9d8 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 26 Feb 2025 15:18:12 -0800 Subject: [PATCH 34/63] Update Effect Duration comment --- Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 50539f10..d8cdb822 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -81,7 +81,8 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") double Delay = 0; - // How long the effect lasts + // How long the effect lasts, 0 for infinite + // Does nothing if effect is instant UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") double Duration = 0; From 273c4b59d028e07c7d3e06960cd8b12cfdb33f10 Mon Sep 17 00:00:00 2001 From: Eric Rajot Date: Thu, 27 Feb 2025 12:30:23 +0100 Subject: [PATCH 35/63] Fixing crash in GMC Acknowledgments. --- Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h b/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h index 766133a8..0a1869a3 100644 --- a/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h +++ b/Source/GMCAbilitySystem/Public/Utility/GMASBoundQueue.h @@ -575,6 +575,10 @@ class GMCABILITYSYSTEM_API TGMASBoundQueue { // Deduct from our ack lifetime; if we've gone stale, remove the stale acks to avoid it just growing forever. TArray FreshAcks; + if (!Acknowledgments.IsValid()) { // that fix the crash, but doesn't fix the issue. @packetdancer + return; + } + auto& Acks = Acknowledgments.GetMutable(); for (auto& Ack : Acks.AckSet) { From bc86cea07c2658882c1e6944532c45618e85971e Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Thu, 27 Feb 2025 23:41:21 -0800 Subject: [PATCH 36/63] Check Application Must/Must Not have tags for Effects --- .../Private/Effects/GMCAbilityEffect.cpp | 12 ++++++++---- .../Public/Effects/GMCAbilityEffect.h | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 21e66f29..bf1aa49e 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -56,8 +56,10 @@ void UGMCAbilityEffect::StartEffect() bHasStarted = true; // Ensure tag requirements are met before applying the effect - if( ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || - DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) + if( ( EffectData.ApplicationMustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.ApplicationMustNotHaveTags) || + ( EffectData.MustHaveTags.Num() > 0 && !DoesOwnerHaveTagFromContainer(EffectData.MustHaveTags) ) || + DoesOwnerHaveTagFromContainer(EffectData.MustNotHaveTags) ) { EndEffect(); return; @@ -67,6 +69,8 @@ void UGMCAbilityEffect::StartEffect() AddAbilitiesToOwner(); EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnActivation); + bHasAppliedEffect = true; + // Instant effects modify base value and end instantly if (EffectData.bIsInstant) { @@ -117,8 +121,8 @@ void UGMCAbilityEffect::EndEffect() UpdateState(EGMASEffectState::Ended, true); } - // Only remove tags and abilities if the effect has started - if (!bHasStarted) return; + // Only remove tags and abilities if the effect has started and applied + if (!bHasStarted || !bHasAppliedEffect) return; if (EffectData.bNegateEffectAtEnd) { diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index d8cdb822..b2d2a6c9 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -234,6 +234,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject private: bool bHasStarted; + bool bHasAppliedEffect; // Used for calculating when to tick Period effects float PrevPeriodMod = 0; From 090c527dcf855fdd7c08f7729ceb1406800a887a Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Fri, 21 Mar 2025 12:38:29 -0700 Subject: [PATCH 37/63] Default abilities to use Anc tick instead of Movement --- Source/GMCAbilitySystem/Public/Ability/GMCAbility.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index ce52bd2e..5e31f0b5 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -210,7 +210,7 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn * Should be set to false for actions that should not be replayed on mispredictions. i.e. firing a weapon */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GMCAbilitySystem") - bool bActivateOnMovementTick = true; + bool bActivateOnMovementTick = false; UFUNCTION() void ServerConfirm(); From bab7a05a56516a7b32862311044cdc25e9658de1 Mon Sep 17 00:00:00 2001 From: JustSomeGuyStudio <89358994+JustSomeGuyStudio@users.noreply.github.com> Date: Tue, 8 Apr 2025 06:02:04 -0400 Subject: [PATCH 38/63] Add Async Hit Data task (#115) * Add UPROPERTY Categories to allow compilation as engine plugin. * Adding Method CancelAbility, allowing ending an ability without triggering EndAbilityEvent. Internally, FinishEndAbility Method as also been added and ensure logics (see GAS termination of an ability) * Moving Activation tag requirement check before instantiation of the ability. * Added Activity Blocked by ActiveAbility * Fixing crash in HandleTaskHeartBeat. * Change behavior for attribute ChangeCallback - Callback OnAttributeChanged is now also called on SP/AP - Callback is now also called when the value is affected outside for whatever reason (like by SetAttributeValueByTag) - Support Replay - SetAttributeValueByTag now also re-caculate the value. Signed-off-by: Eric Rajot * BL-279 Clean before merge - Removed Log message - Removed irrelevant bounding Signed-off-by: Eric Rajot * BL-279 Bug fixing and improvement for rep notify * BL-279 Fixing Attribute notification * BL-279 Adding Byte data type to Set Target for DW GMC Ability * Improvement for tags, added duration * BL-232 Progress * BL-232 Initial work on External Effect/Ability Pending and Tag application * BL-232 Working state. * BL-232 Refactor and cleaning, handled now by Instanced Struct * BL-232 Progress of the day * Fixing a bug in remove effect. They are now removing by specifique ID when outer removed, to ensure the list rest coherent client <-> server * BL-232 Fixing removing effect * BL-232 bug fixing in effect * Bug fixing, adding accessor * BL-232 Fix effect remove itself even is another instance is running * Added getter * Stability - Fixed name space for SetTargetDataFloat, - Fixed EEffectType of GMCAbilityEffect sharing same name than playfab - Fixed wait for input key release with suggestion of Nas - GetAbilityMapData now return a const ref. For compability, you probably want to add to core redirect those lines : ``` +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectType",NewName="/Script/GMCAbilitySystem.EGMASEffectType") +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectState",NewName="/Script/GMCAbilitySystem.EGMASEffectState") ``` * Adding possibility for effect to end an active ability * Changing Ability Blocking way. They are now internally stored. An Ability store itself what ability it will block, instead of other ability who will block him. This allow to modify during execution this behavior. For example, you may be want to stop an ability activation only during X time in your ability execution. * Adding a nicer way to end ability * BL-225 Grenade 100% * Fix clang error * Adding Attribute Dynamic Condition to effect. * Fix crash when an active effect has been destroyed * Module upload * GMC Update * Addition of levitation actor * Crash fix * GMC Fix for starting abilities, Fix for stamina, Fix for crash, Adding speed for orb, Adding plugin for metahuman hairs. * Update for log * Fix for GetActiveEffect ? * Typo fix * couple of misc fixes from rebasing deep worlds fork * Add SetTargetHit * Revert "Merge remote-tracking branch 'upstream/dw_dev' into OMS_CustomTasks" This reverts commit 158128399c26580e69f7ec0108ada20c78f01526, reversing changes made to 1fbe07aa64219b25c68b2bc21cb28de73ee763f4. --------- Signed-off-by: Eric Rajot Co-authored-by: Eric Rajot Co-authored-by: Peter Gilbert --- .../Ability/Tasks/SetTargetDataHit.cpp | 44 +++++++++++++++++ .../Public/Ability/Tasks/SetTargetDataHit.h | 48 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp new file mode 100644 index 00000000..f22f5d93 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp @@ -0,0 +1,44 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Ability/Tasks/SetTargetDataHit.h" +#include "GMCAbilityComponent.h" + +UGMCAbilityTask_SetTargetDataHit* UGMCAbilityTask_SetTargetDataHit::SetTargetDataHit(UGMCAbility* OwningAbility, FHitResult InHit) +{ + UGMCAbilityTask_SetTargetDataHit* Task = NewAbilityTask(OwningAbility); + Task->Ability = OwningAbility; + Task->Target = InHit; + return Task; +} + +void UGMCAbilityTask_SetTargetDataHit::Activate() +{ + Super::Activate(); + + if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + { + ClientProgressTask(); + } +} + +void UGMCAbilityTask_SetTargetDataHit::ProgressTask(FInstancedStruct& TaskData) +{ + Super::ProgressTask(TaskData); + const FGMCAbilityTaskTargetDataHit Data = TaskData.Get(); + + Completed.Broadcast(Data.Target); + EndTask(); +} + +void UGMCAbilityTask_SetTargetDataHit::ClientProgressTask() +{ + FGMCAbilityTaskTargetDataHit TaskData; + TaskData.TaskType = EGMCAbilityTaskDataType::Progress; + TaskData.AbilityID = Ability->GetAbilityID(); + TaskData.TaskID = TaskID; + TaskData.Target = Target; + const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData); + + Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance); +} \ No newline at end of file diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h new file mode 100644 index 00000000..220b8499 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h @@ -0,0 +1,48 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GMCAbilityTaskBase.h" +#include "GMCAbilityTaskData.h" +#include "Ability/GMCAbility.h" +#include "LatentActions.h" +#include "SetTargetDataHit.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUGMCAbilityTaskTargetDataHitAsyncActionPin, FHitResult, target); + +USTRUCT(BlueprintType) +struct FGMCAbilityTaskTargetDataHit : public FGMCAbilityTaskData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + FHitResult Target{FHitResult()}; +}; + + +/** + * + */ +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataHit : public UGMCAbilityTaskBase +{ + GENERATED_BODY() + +public: + + UPROPERTY(BlueprintAssignable) + FUGMCAbilityTaskTargetDataHitAsyncActionPin Completed; + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + FHitResult Target; + + virtual void ProgressTask(FInstancedStruct& TaskData) override; + virtual void ClientProgressTask() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Hit Result)",Category = "GMCAbilitySystem/Tasks") + static UGMCAbilityTask_SetTargetDataHit* SetTargetDataHit(UGMCAbility* OwningAbility, FHitResult InHit); + + //Overriding BP async action base + virtual void Activate() override; +}; From d927fc5f9fc48572858be2475d3c01151b430d76 Mon Sep 17 00:00:00 2001 From: Baja Short Long Date: Tue, 8 Apr 2025 03:03:05 -0700 Subject: [PATCH 39/63] Allow for overriding block and cancel methods (#113) --- .../Private/Ability/GMCAbility.cpp | 27 +++++++++++-------- .../Public/Ability/GMCAbility.h | 2 ++ .../Public/Components/GMCAbilityComponent.h | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 2e1670e1..acd90230 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -170,6 +170,20 @@ void UGMCAbility::HandleTaskHeartbeat(int TaskID) } } +void UGMCAbility::CancelAbilities() +{ + for (const auto& AbilityToCancelTag : CancelAbilitiesWithTag) { + if (AbilityTag == AbilityToCancelTag) { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability (tag) %s is trying to cancel itself, if you attempt to reset the ability, please use //TODO instead"), *AbilityTag.ToString()); + continue; + } + + if (OwnerAbilityComponent->EndAbilitiesByTag(AbilityToCancelTag)) { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s has been cancelled by (tag) %s"), *AbilityTag.ToString(), *AbilityToCancelTag.ToString()); + } + } +} + void UGMCAbility::ServerConfirm() { bServerConfirmed = true; @@ -284,18 +298,9 @@ void UGMCAbility::BeginAbility() // Initialize Ability AbilityState = EAbilityState::Initialized; - + // Cancel Abilities in CancelAbilitiesWithTag container - for (const auto& AbilityToCancelTag : CancelAbilitiesWithTag) { - if (AbilityTag == AbilityToCancelTag) { - UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability (tag) %s is trying to cancel itself, if you attempt to reset the ability, please use //TODO instead"), *AbilityTag.ToString()); - continue; - } - - if (OwnerAbilityComponent->EndAbilitiesByTag(AbilityToCancelTag)) { - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s has been cancelled by (tag) %s"), *AbilityTag.ToString(), *AbilityToCancelTag.ToString()); - } - } + CancelAbilities(); // Execute BP Event BeginAbilityEvent(); diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index 5e31f0b5..f8aa9341 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -205,6 +205,8 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn // Prevent Abilities with these tags from activating when this ability is activated FGameplayTagContainer BlockOtherAbility; + virtual void CancelAbilities(); + /** * If true, activate on movement tick, if false, activate on ancillary tick. Defaults to true. * Should be set to false for actions that should not be replayed on mispredictions. i.e. firing a weapon diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index d4e1330a..50724560 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -205,7 +205,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo int32 GetActiveAbilityCount(TSubclassOf AbilityClass); // Perform a check in every active ability against BlockOtherAbility and check if the tag provided is present - bool IsAbilityTagBlocked(const FGameplayTag AbilityTag) const; + virtual bool IsAbilityTagBlocked(const FGameplayTag AbilityTag) const; UFUNCTION(BlueprintCallable, DisplayName="End Abilities (By Tag)", Category="GMAS|Abilities") // End all abilities with the corresponding tag, returns the number of abilities ended From 8603321b57002f7677ded954c1f85adaaaf78a0d Mon Sep 17 00:00:00 2001 From: Ryan Lim Date: Tue, 8 Apr 2025 18:04:07 +0800 Subject: [PATCH 40/63] - Added SetTargetDataInstancedStruct and SetTargetDataTransform ability tasks. (#111) --- .../Tasks/SetTargetDataInstancedStruct.cpp | 39 +++++++++++++++ .../Ability/Tasks/SetTargetDataTransform.cpp | 39 +++++++++++++++ .../Tasks/SetTargetDataInstancedStruct.h | 47 +++++++++++++++++++ .../Ability/Tasks/SetTargetDataTransform.h | 47 +++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp new file mode 100644 index 00000000..1c938a2e --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp @@ -0,0 +1,39 @@ +#include "Ability/Tasks/SetTargetDataInstancedStruct.h" +#include "GMCAbilityComponent.h" + +UGMCAbilityTask_SetTargetDataInstancedStruct* UGMCAbilityTask_SetTargetDataInstancedStruct::SetTargetDataInstancedStruct(UGMCAbility* OwningAbility, FInstancedStruct InstancedStruct){ + UGMCAbilityTask_SetTargetDataInstancedStruct* Task = NewAbilityTask(OwningAbility); + Task->Ability = OwningAbility; + Task->Target = InstancedStruct; + return Task; +} + +void UGMCAbilityTask_SetTargetDataInstancedStruct::Activate(){ + Super::Activate(); + + if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + { + ClientProgressTask(); + } +} + +void UGMCAbilityTask_SetTargetDataInstancedStruct::ProgressTask(FInstancedStruct& TaskData){ + Super::ProgressTask(TaskData); + const FGMCAbilityTaskTargetDataInstancedStruct Data = TaskData.Get(); + + Completed.Broadcast(Data.Target); + EndTask(); +} + +void UGMCAbilityTask_SetTargetDataInstancedStruct::ClientProgressTask(){ + FGMCAbilityTaskTargetDataInstancedStruct TaskData; + TaskData.TaskType = EGMCAbilityTaskDataType::Progress; + TaskData.AbilityID = Ability->GetAbilityID(); + TaskData.TaskID = TaskID; + TaskData.Target = Target; + const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData); + + Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance); +} + + diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp new file mode 100644 index 00000000..ca1f9cd1 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp @@ -0,0 +1,39 @@ +#include "Ability/Tasks/SetTargetDataTransform.h" +#include "GMCAbilityComponent.h" + +UGMCAbilityTask_SetTargetDataTransform* UGMCAbilityTask_SetTargetDataTransform::SetTargetDataTransform(UGMCAbility* OwningAbility, FTransform Transform){ + UGMCAbilityTask_SetTargetDataTransform* Task = NewAbilityTask(OwningAbility); + Task->Ability = OwningAbility; + Task->Target = Transform; + return Task; +} + +void UGMCAbilityTask_SetTargetDataTransform::Activate(){ + Super::Activate(); + + if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + { + ClientProgressTask(); + } +} + +void UGMCAbilityTask_SetTargetDataTransform::ProgressTask(FInstancedStruct& TaskData){ + Super::ProgressTask(TaskData); + const FGMCAbilityTaskTargetDataTransform Data = TaskData.Get(); + + Completed.Broadcast(Data.Target); + EndTask(); +} + +void UGMCAbilityTask_SetTargetDataTransform::ClientProgressTask(){ + FGMCAbilityTaskTargetDataTransform TaskData; + TaskData.TaskType = EGMCAbilityTaskDataType::Progress; + TaskData.AbilityID = Ability->GetAbilityID(); + TaskData.TaskID = TaskID; + TaskData.Target = Target; + const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData); + + Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance); +} + + diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h new file mode 100644 index 00000000..f6259f3d --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h @@ -0,0 +1,47 @@ +// + +#pragma once + +#include "CoreMinimal.h" +#include "GMCAbilityTaskBase.h" +#include "GMCAbilityTaskData.h" +#include "Ability/GMCAbility.h" +#include "LatentActions.h" +#include "SetTargetDataInstancedStruct.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUGMCAbilityTaskTargetDataInstancedStructAsyncActionPin, FInstancedStruct, Target); + + +USTRUCT(BlueprintType) +struct FGMCAbilityTaskTargetDataInstancedStruct : public FGMCAbilityTaskData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + FInstancedStruct Target{FInstancedStruct()}; +}; + +/** + * + */ +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataInstancedStruct : public UGMCAbilityTaskBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FUGMCAbilityTaskTargetDataInstancedStructAsyncActionPin Completed; + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + FInstancedStruct Target; + + virtual void ProgressTask(FInstancedStruct& TaskData) override; + virtual void ClientProgressTask() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Instanced Struct)",Category = "GMCAbilitySystem/Tasks") + static UGMCAbilityTask_SetTargetDataInstancedStruct* SetTargetDataInstancedStruct(UGMCAbility* OwningAbility, FInstancedStruct InstancedStruct); + + //Overriding BP async action base + virtual void Activate() override; +}; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h new file mode 100644 index 00000000..cd6a3a9b --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h @@ -0,0 +1,47 @@ +// + +#pragma once + +#include "CoreMinimal.h" +#include "GMCAbilityTaskBase.h" +#include "GMCAbilityTaskData.h" +#include "Ability/GMCAbility.h" +#include "LatentActions.h" +#include "SetTargetDataTransform.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUGMCAbilityTaskTargetDataTransformAsyncActionPin, FTransform, Target); + + +USTRUCT(BlueprintType) +struct FGMCAbilityTaskTargetDataTransform : public FGMCAbilityTaskData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + FTransform Target{FTransform()}; +}; + +/** + * + */ +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataTransform : public UGMCAbilityTaskBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FUGMCAbilityTaskTargetDataTransformAsyncActionPin Completed; + + UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") + FTransform Target; + + virtual void ProgressTask(FInstancedStruct& TaskData) override; + virtual void ClientProgressTask() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Transform)",Category = "GMCAbilitySystem/Tasks") + static UGMCAbilityTask_SetTargetDataTransform* SetTargetDataTransform(UGMCAbility* OwningAbility, FTransform Transform); + + //Overriding BP async action base + virtual void Activate() override; +}; From 692ea1fa036389483f0e6df0ae9ce55830881dbc Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 29 Apr 2025 16:38:09 -0700 Subject: [PATCH 41/63] Various Listen Server fixes --- .../Ability/Tasks/GMCAbilityTaskBase.cpp | 18 +++++++- .../Ability/Tasks/SetTargetDataByte.cpp | 2 +- .../Ability/Tasks/SetTargetDataFloat.cpp | 2 +- .../Tasks/SetTargetDataGameplayTag.cpp | 2 +- .../Ability/Tasks/SetTargetDataHit.cpp | 2 +- .../Tasks/SetTargetDataInstancedStruct.cpp | 2 +- .../Ability/Tasks/SetTargetDataInt.cpp | 2 +- .../Ability/Tasks/SetTargetDataObject.cpp | 2 +- .../Ability/Tasks/SetTargetDataTransform.cpp | 2 +- .../Ability/Tasks/SetTargetDataVector3.cpp | 9 +++- .../Ability/Tasks/WaitForInputKeyRelease.cpp | 30 +++++++++---- .../Components/GMCAbilityComponent.cpp | 42 ++++++++++--------- ...eplayDebuggerCategory_GMCAbilitySystem.cpp | 4 +- .../Public/Ability/Tasks/GMCAbilityTaskBase.h | 15 ++++--- .../Ability/Tasks/WaitForInputKeyRelease.h | 12 ++++-- .../Public/Components/GMCAbilityComponent.h | 6 +++ .../Public/Effects/GMCAbilityEffect.h | 3 +- 17 files changed, 106 insertions(+), 49 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp index 798eb8c1..f15658bc 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp @@ -34,7 +34,8 @@ void UGMCAbilityTaskBase::Tick(float DeltaTime) if (AbilitySystemComponent->GMCMovementComponent->IsLocallyControlledServerPawn()) return; // If not the server version of the component, send heartbeats - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer && + AbilitySystemComponent->GetNetMode() != NM_ListenServer) { if (ClientLastHeartbeatSentTime + HeartbeatInterval < AbilitySystemComponent->ActionTimer) { @@ -48,7 +49,6 @@ void UGMCAbilityTaskBase::Tick(float DeltaTime) Ability->EndAbility(); EndTask(); } - } void UGMCAbilityTaskBase::AncillaryTick(float DeltaTime){ @@ -64,3 +64,17 @@ void UGMCAbilityTaskBase::ClientProgressTask() const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData); Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance); } + +void UGMCAbilityTaskBase::Heartbeat() +{ + AbilitySystemComponent->GMCMovementComponent->SV_SwapServerState(); + LastHeartbeatReceivedTime = AbilitySystemComponent->ActionTimer; + AbilitySystemComponent->GMCMovementComponent->SV_SwapServerState(); +} + +bool UGMCAbilityTaskBase::IsClientOrRemoteListenServerPawn() const +{ + return (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer && + AbilitySystemComponent->GetNetMode() != NM_ListenServer) || + AbilitySystemComponent->GMCMovementComponent->IsLocallyControlledServerPawn(); +} diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp index 79883141..a7cf416f 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataByte.cpp @@ -16,7 +16,7 @@ void UGMCAbilityTask_SetTargetDataByte::Activate() { Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataFloat.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataFloat.cpp index dd7df7f0..f75bc9f2 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataFloat.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataFloat.cpp @@ -16,7 +16,7 @@ void UGMCAbilityTask_SetTargetDataFloat::Activate() { Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp index 6847bc08..9a122df0 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataGameplayTag.cpp @@ -11,7 +11,7 @@ UGMCAbilityTask_SetTargetDataGameplayTag* UGMCAbilityTask_SetTargetDataGameplayT void UGMCAbilityTask_SetTargetDataGameplayTag::Activate(){ Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp index f22f5d93..29bca21e 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataHit.cpp @@ -16,7 +16,7 @@ void UGMCAbilityTask_SetTargetDataHit::Activate() { Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp index 1c938a2e..0cc784b3 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInstancedStruct.cpp @@ -11,7 +11,7 @@ UGMCAbilityTask_SetTargetDataInstancedStruct* UGMCAbilityTask_SetTargetDataInsta void UGMCAbilityTask_SetTargetDataInstancedStruct::Activate(){ Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInt.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInt.cpp index 11d3353c..d29774e8 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInt.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataInt.cpp @@ -16,7 +16,7 @@ void UGMCAbilityTask_SetTargetDataInt::Activate() { Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataObject.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataObject.cpp index ca7852c9..c848c8df 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataObject.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataObject.cpp @@ -16,7 +16,7 @@ void UGMCAbilityTask_SetTargetDataObject::Activate() { Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp index ca1f9cd1..076bf3a6 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataTransform.cpp @@ -11,7 +11,7 @@ UGMCAbilityTask_SetTargetDataTransform* UGMCAbilityTask_SetTargetDataTransform:: void UGMCAbilityTask_SetTargetDataTransform::Activate(){ Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataVector3.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataVector3.cpp index c540e05f..c5f55b47 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataVector3.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/SetTargetDataVector3.cpp @@ -16,7 +16,7 @@ void UGMCAbilityTask_SetTargetDataVector3::Activate() { Super::Activate(); - if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer) + if (IsClientOrRemoteListenServerPawn()) { ClientProgressTask(); } @@ -25,6 +25,13 @@ void UGMCAbilityTask_SetTargetDataVector3::Activate() void UGMCAbilityTask_SetTargetDataVector3::ProgressTask(FInstancedStruct& TaskData) { Super::ProgressTask(TaskData); + if (TaskData.GetScriptStruct() != FGMCAbilityTaskTargetDataVector3::StaticStruct()) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("UGMCAbilityTask_SetTargetDataVector3::ProgressTask: Invalid TaskData")); + EndTask(); + return; + } + const FGMCAbilityTaskTargetDataVector3 Data = TaskData.Get(); Completed.Broadcast(Data.Target); diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index 77d4396e..72f22241 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -5,17 +5,20 @@ #include "Components/GMCAbilityComponent.h" #include "Kismet/KismetSystemLibrary.h" -UGMCAbilityTask_WaitForInputKeyRelease* UGMCAbilityTask_WaitForInputKeyRelease::WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation) +UGMCAbilityTask_WaitForInputKeyRelease* UGMCAbilityTask_WaitForInputKeyRelease::WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation, float MaxDuration) { UGMCAbilityTask_WaitForInputKeyRelease* Task = NewAbilityTask(OwningAbility); Task->Ability = OwningAbility; Task->bShouldCheckForReleaseDuringActivation = bCheckForReleaseDuringActivation; + Task->MaxDuration = MaxDuration; return Task; } void UGMCAbilityTask_WaitForInputKeyRelease::Activate() { Super::Activate(); + + StartTime = AbilitySystemComponent->ActionTimer; UEnhancedInputComponent* const InputComponent = GetEnhancedInputComponent(); @@ -52,15 +55,22 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Activate() } } -void UGMCAbilityTask_WaitForInputKeyRelease::OnKeyReleased(const FInputActionValue& InputActionValue) +void UGMCAbilityTask_WaitForInputKeyRelease::Tick(float DeltaTime) { - // Unbind since we're done now. - ClientProgressTask(); - if (UInputComponent* const InputComponent = GetValid(GetEnhancedInputComponent())) + Super::Tick(DeltaTime); + Duration = AbilitySystemComponent->ActionTimer - StartTime; + OnTick.Broadcast(Duration); + + if (MaxDuration > 0 && Duration >= MaxDuration) { - InputComponent->RemoveActionBindingForHandle(InputBindingHandle); + ClientProgressTask(); } +} +void UGMCAbilityTask_WaitForInputKeyRelease::OnKeyReleased(const FInputActionValue& InputActionValue) +{ + // Unbind since we're done now. + ClientProgressTask(); InputBindingHandle = -1; } @@ -80,7 +90,8 @@ UEnhancedInputComponent* UGMCAbilityTask_WaitForInputKeyRelease::GetEnhancedInpu void UGMCAbilityTask_WaitForInputKeyRelease::OnTaskCompleted() { EndTask(); - Completed.Broadcast(); + Duration = AbilitySystemComponent->ActionTimer - StartTime; + Completed.Broadcast(Duration); bTaskCompleted = true; } @@ -108,6 +119,11 @@ void UGMCAbilityTask_WaitForInputKeyRelease::ProgressTask(FInstancedStruct& Task void UGMCAbilityTask_WaitForInputKeyRelease::ClientProgressTask() { + if (UInputComponent* const InputComponent = GetValid(GetEnhancedInputComponent())) + { + InputComponent->RemoveActionBindingForHandle(InputBindingHandle); + } + FGMCAbilityTaskData TaskData; TaskData.TaskType = EGMCAbilityTaskDataType::Progress; TaskData.AbilityID = Ability->GetAbilityID(); diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 3e8c36e6..79ff5407 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -82,12 +82,6 @@ void UGMC_AbilitySystemComponent::BindReplicationData() // We sort our attributes alphabetically by tag so that it's deterministic. for (auto& AttributeForBind : BoundAttributes.Attributes) { - GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.BaseValue, - EGMC_PredictionMode::ServerAuth_Output_ClientValidated, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::Periodic_Output, - EGMC_InterpolationFunction::TargetValue); - GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.Value, EGMC_PredictionMode::ServerAuth_Output_ClientValidated, EGMC_CombineMode::CombineIfUnchanged, @@ -113,13 +107,6 @@ void UGMC_AbilitySystemComponent::BindReplicationData() EGMC_InterpolationFunction::TargetValue); } - // Sync'd Action Timer - GMCMovementComponent->BindDoublePrecisionFloat(ActionTimer, - EGMC_PredictionMode::ServerAuth_Output_ClientValidated, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::None, - EGMC_InterpolationFunction::TargetValue); - // Granted Abilities GMCMovementComponent->BindGameplayTagContainer(GrantedAbilityTags, EGMC_PredictionMode::ServerAuth_Output_ClientValidated, @@ -538,7 +525,8 @@ bool UGMC_AbilitySystemComponent::IsServerOnly() const void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) { bJustTeleported = false; - ActionTimer += DeltaTime; + // ActionTimer += DeltaTime; + ActionTimer = GMCMovementComponent->GetMoveTimestamp(); ApplyStartingEffects(); @@ -1084,6 +1072,20 @@ bool UGMC_AbilitySystemComponent::CheckActivationTags(const UGMCAbility* Ability } +void UGMC_AbilitySystemComponent::ClearAbilityMap() +{ + // For each AbilityMap in the map AbilityMap: + for (auto& AbilityMapData : AbilityMap) + { + if (GrantedAbilityTags.HasTag(AbilityMapData.Value.InputTag)) + { + GrantedAbilityTags.RemoveTag(AbilityMapData.Value.InputTag); + } + } + + AbilityMap.Empty(); +} + void UGMC_AbilitySystemComponent::InitializeAbilityMap(){ for (UGMCAbilityMapData* StartingAbilityMap : AbilityMaps) { @@ -1121,12 +1123,12 @@ void UGMC_AbilitySystemComponent::RemoveAbilityMapData(const FAbilityMapData& Ab { AbilityMap.Remove(AbilityMapData.InputTag); } + + if (GrantedAbilityTags.HasTag(AbilityMapData.InputTag)) { - if (GrantedAbilityTags.HasTag(AbilityMapData.InputTag)) - { - GrantedAbilityTags.RemoveTag(AbilityMapData.InputTag); - } + GrantedAbilityTags.RemoveTag(AbilityMapData.InputTag); } + } void UGMC_AbilitySystemComponent::InitializeStartingAbilities() @@ -1689,7 +1691,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Ids, EGMCAbil if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone && !bInAncillaryTick) { - ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), + ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); diff --git a/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp b/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp index 5d7b9cb0..515edddb 100644 --- a/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp +++ b/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp @@ -18,15 +18,17 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::CollectData(APlayerController* if (DebugActor) { DataPack.ActorName = DebugActor->GetName(); - + if (const UGMC_AbilitySystemComponent* AbilityComponent = DebugActor->FindComponentByClass()) { + AbilityComponent->GMCMovementComponent->SV_SwapServerState(); DataPack.GrantedAbilities = AbilityComponent->GetGrantedAbilities().ToStringSimple(); DataPack.ActiveTags = AbilityComponent->GetActiveTags().ToStringSimple(); DataPack.Attributes = AbilityComponent->GetAllAttributesString(); DataPack.ActiveEffects = AbilityComponent->GetActiveEffectsString(); DataPack.ActiveEffectData = AbilityComponent->GetActiveEffectsDataString(); DataPack.ActiveAbilities = AbilityComponent->GetActiveAbilitiesString(); + AbilityComponent->GMCMovementComponent->SV_SwapServerState(); } } } diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h index 7deb59bc..f2795a31 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h @@ -64,10 +64,8 @@ class GMCABILITYSYSTEM_API UGMCAbilityTaskBase : public UGameplayTask // Task must make sure this is handled properly virtual void ClientProgressTask(); - void Heartbeat() - { - LastHeartbeatReceivedTime = AbilitySystemComponent->ActionTimer; - }; + virtual void Heartbeat(); + protected: bool bTaskCompleted; @@ -75,13 +73,18 @@ class GMCABILITYSYSTEM_API UGMCAbilityTaskBase : public UGameplayTask /** Task Owner that created us */ TWeakObjectPtr TaskOwner; + // Whether this task is running on a client or a client on a listen server + bool IsClientOrRemoteListenServerPawn() const; + private: // How often client sends heartbeats to server - float HeartbeatInterval = .5f; + float HeartbeatInterval = .1f; // Max time between heartbeats before server cancels task - float HeartbeatMaxInterval = 1.f; + float HeartbeatMaxInterval =.3f; float ClientLastHeartbeatSentTime; float LastHeartbeatReceivedTime; + + }; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h index 450ce726..b6bd02d2 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h @@ -6,7 +6,7 @@ #include "WaitForInputKeyRelease.generated.h" // DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUWaitForInputKeyReleaseAsyncActionPin, float, DurationHeld); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGMCAbilityTaskWaitForInputKeyRelease); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGMCAbilityTaskWaitForInputKeyRelease, float, Duration); UCLASS() @@ -29,14 +29,19 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA * @return */ UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Release",Category = "GMCAbilitySystem/Tasks") - static UGMCAbilityTask_WaitForInputKeyRelease* WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation = true); + static UGMCAbilityTask_WaitForInputKeyRelease* WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation = true, float MaxDuration = 0.0f); //Overriding BP async action base virtual void Activate() override; + virtual void Tick(float DeltaTime) override; + UPROPERTY(BlueprintAssignable) FGMCAbilityTaskWaitForInputKeyRelease Completed; + UPROPERTY(BlueprintAssignable) + FGMCAbilityTaskWaitForInputKeyRelease OnTick; + protected: void OnKeyReleased(const FInputActionValue& InputActionValue); @@ -50,8 +55,9 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA UEnhancedInputComponent* GetEnhancedInputComponent() const; int64 InputBindingHandle = -1; + + float MaxDuration; - // Todo: Add duration back in float StartTime; float Duration; double OldTime; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 50724560..eb7f148c 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -555,6 +555,12 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Map of Ability Tags to Ability Classes TMap AbilityMap; +public: + // Empty the AbilityMap and remove all granted abilities from existing maps + UFUNCTION(BlueprintCallable) + void ClearAbilityMap(); + +private: // List of filtered tag delegates to call when tags change. TArray> FilteredTagDelegates; diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index b2d2a6c9..cc4c3c2a 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -183,7 +183,8 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") void InitializeEffect(FGMCAbilityEffectData InitializationData); - + + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") void EndEffect(); virtual void BeginDestroy() override; From 9fe5233e10f7f1bcabd2f6ae13e1986d42cc2a4a Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 07:02:51 -0700 Subject: [PATCH 42/63] fix WaitForInputKeyRelease firing multiple times on timeout --- .../Private/Ability/Tasks/WaitForInputKeyRelease.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index 72f22241..30b45481 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -61,7 +61,7 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Tick(float DeltaTime) Duration = AbilitySystemComponent->ActionTimer - StartTime; OnTick.Broadcast(Duration); - if (MaxDuration > 0 && Duration >= MaxDuration) + if (MaxDuration > 0 && Duration >= MaxDuration && !bTaskCompleted) { ClientProgressTask(); } From 5d9dd7973edea8552877cb43a1ae5d39b86cfe1b Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 07:04:27 -0700 Subject: [PATCH 43/63] Slightly cleaner fix for WaitForInputKeyRelease --- .../Private/Ability/Tasks/WaitForInputKeyRelease.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index 30b45481..36176271 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -58,10 +58,12 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Activate() void UGMCAbilityTask_WaitForInputKeyRelease::Tick(float DeltaTime) { Super::Tick(DeltaTime); + if (bTaskCompleted) return; + Duration = AbilitySystemComponent->ActionTimer - StartTime; OnTick.Broadcast(Duration); - if (MaxDuration > 0 && Duration >= MaxDuration && !bTaskCompleted) + if (MaxDuration > 0 && Duration >= MaxDuration) { ClientProgressTask(); } From 9b80ef7e62b2f39600c5e75ed20b98ea7009eb2f Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 07:10:12 -0700 Subject: [PATCH 44/63] Experimental: Have ability tasks tick before ability ticks --- .../Private/Components/GMCAbilityComponent.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 79ff5407..c26198f9 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -159,6 +159,8 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb TickActiveCooldowns(DeltaTime); + + SendTaskDataToActiveAbility(false); TickAncillaryActiveAbilities(DeltaTime); // Check if we have a valid operation @@ -167,8 +169,6 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb { ProcessAbilityOperation(Operation, false); } - - SendTaskDataToActiveAbility(false); ClearAbilityAndTaskData(); QueuedEffectOperations_ClientAuth.ClearCurrentOperation(); @@ -529,8 +529,10 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) ActionTimer = GMCMovementComponent->GetMoveTimestamp(); ApplyStartingEffects(); - + + SendTaskDataToActiveAbility(true); TickActiveAbilities(DeltaTime); + TickActiveEffects(DeltaTime); // Abilities @@ -550,7 +552,7 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) ServerHandlePredictedPendingEffect(DeltaTime); - SendTaskDataToActiveAbility(true); + } void UGMC_AbilitySystemComponent::GenSimulationTick(float DeltaTime) From 2e1a47ce76d16d1590bbacbd532cada55f3422aa Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 07:45:33 -0700 Subject: [PATCH 45/63] Experimental: Move majority of Task logic to happen in Anc tick instead of Pred tick --- .../Private/Ability/Tasks/GMCAbilityTaskBase.cpp | 8 ++++---- .../Private/Ability/Tasks/WaitForInputKeyRelease.cpp | 2 +- .../Public/Ability/Tasks/WaitForInputKeyRelease.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp index f15658bc..92ca9f1d 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp @@ -30,6 +30,10 @@ void UGMCAbilityTaskBase::RegisterTask(UGMCAbilityTaskBase* Task) void UGMCAbilityTaskBase::Tick(float DeltaTime) { + +} + +void UGMCAbilityTaskBase::AncillaryTick(float DeltaTime){ // Locally controlled server pawns don't need to send heartbeats if (AbilitySystemComponent->GMCMovementComponent->IsLocallyControlledServerPawn()) return; @@ -51,10 +55,6 @@ void UGMCAbilityTaskBase::Tick(float DeltaTime) } } -void UGMCAbilityTaskBase::AncillaryTick(float DeltaTime){ - -} - void UGMCAbilityTaskBase::ClientProgressTask() { FGMCAbilityTaskData TaskData; diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index 36176271..1c731eb3 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -55,7 +55,7 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Activate() } } -void UGMCAbilityTask_WaitForInputKeyRelease::Tick(float DeltaTime) +void UGMCAbilityTask_WaitForInputKeyRelease::AncillaryTick(float DeltaTime) { Super::Tick(DeltaTime); if (bTaskCompleted) return; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h index b6bd02d2..59415f69 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h @@ -34,7 +34,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA //Overriding BP async action base virtual void Activate() override; - virtual void Tick(float DeltaTime) override; + virtual void AncillaryTick(float DeltaTime) override; UPROPERTY(BlueprintAssignable) FGMCAbilityTaskWaitForInputKeyRelease Completed; From e032b7616b235445a0980cd61e1c64da54d5199a Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 08:22:34 -0700 Subject: [PATCH 46/63] Experimental: ability executes BeginAbility before Ticking --- Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp | 6 ++++++ .../Private/Ability/Tasks/WaitForInputKeyPress.cpp | 2 +- Source/GMCAbilitySystem/Public/Ability/GMCAbility.h | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index acd90230..f82ecea8 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -33,6 +33,9 @@ UWorld* UGMCAbility::GetWorld() const void UGMCAbility::Tick(float DeltaTime) { + // Don't tick before the ability is initialized + if (AbilityState == EAbilityState::PreExecution) return; + if (!OwnerAbilityComponent->HasAuthority()) { if (!bServerConfirmed && ClientStartTime + ServerConfirmTimeout < OwnerAbilityComponent->ActionTimer) @@ -53,6 +56,9 @@ void UGMCAbility::Tick(float DeltaTime) } void UGMCAbility::AncillaryTick(float DeltaTime){ + // Don't tick before the ability is initialized + if (AbilityState == EAbilityState::PreExecution) return; + AncillaryTickTasks(DeltaTime); AncillaryTickEvent(DeltaTime); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp index 2e18f150..5046ff93 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp @@ -47,7 +47,7 @@ void UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed(const FInputActionValue& InputComponent->RemoveActionBindingForHandle(InputBindingHandle); InputBindingHandle = -1; } - + ClientProgressTask(); } diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index f8aa9341..690e4f37 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -11,6 +11,7 @@ UENUM(BlueprintType) enum class EAbilityState : uint8 { + PreExecution, Initialized, Running, Waiting, @@ -36,7 +37,7 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn //// Ability State // EAbilityState. Use Getters/Setters UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") - EAbilityState AbilityState; + EAbilityState AbilityState = EAbilityState::PreExecution; // Data used to execute this ability UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") From e31b8653ca592150d4fe6e39ed20d8c74cc926d8 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 08:30:05 -0700 Subject: [PATCH 47/63] Experimental: ability no longer ticks after EndAbility --- Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index f82ecea8..9ec0413b 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -33,8 +33,8 @@ UWorld* UGMCAbility::GetWorld() const void UGMCAbility::Tick(float DeltaTime) { - // Don't tick before the ability is initialized - if (AbilityState == EAbilityState::PreExecution) return; + // Don't tick before the ability is initialized or after it has ended + if (AbilityState == EAbilityState::PreExecution || AbilityState == EAbilityState::Ended) return; if (!OwnerAbilityComponent->HasAuthority()) { @@ -56,8 +56,8 @@ void UGMCAbility::Tick(float DeltaTime) } void UGMCAbility::AncillaryTick(float DeltaTime){ - // Don't tick before the ability is initialized - if (AbilityState == EAbilityState::PreExecution) return; + // Don't tick before the ability is initialized or after it has ended + if (AbilityState == EAbilityState::PreExecution || AbilityState == EAbilityState::Ended) return; AncillaryTickTasks(DeltaTime); AncillaryTickEvent(DeltaTime); From 018c8b853b3978fddbd1065e5bad386465127bc1 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 10:36:05 -0700 Subject: [PATCH 48/63] Add a TimedOut pin for KeyRelease and KeyPress. Allow KeyPress to check if key is currently pressed at start --- .../Ability/Tasks/WaitForInputKeyPress.cpp | 70 +++++++++++++++---- .../Ability/Tasks/WaitForInputKeyRelease.cpp | 12 +++- .../Ability/Tasks/WaitForInputKeyPress.h | 27 ++++++- .../Ability/Tasks/WaitForInputKeyRelease.h | 6 ++ 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp index 5046ff93..87ec3be0 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp @@ -1,36 +1,53 @@ #include "Ability/Tasks/WaitForInputKeyPress.h" #include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" #include "Components/GMCAbilityComponent.h" -UGMCAbilityTask_WaitForInputKeyPress* UGMCAbilityTask_WaitForInputKeyPress::WaitForKeyPress(UGMCAbility* OwningAbility) +UGMCAbilityTask_WaitForInputKeyPress* UGMCAbilityTask_WaitForInputKeyPress::WaitForKeyPress(UGMCAbility* OwningAbility, bool bCheckForPressDuringActivation, float MaxDuration) { UGMCAbilityTask_WaitForInputKeyPress* Task = NewAbilityTask(OwningAbility); Task->Ability = OwningAbility; + Task->bShouldCheckForPressDuringActivation = bCheckForPressDuringActivation; + Task->MaxDuration = MaxDuration; return Task; } void UGMCAbilityTask_WaitForInputKeyPress::Activate() { Super::Activate(); - + + StartTime = AbilitySystemComponent->ActionTimer; + if (Ability->bAllowMultipleInstances) { UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability %s is set to allow multiple instances and this should not be used with WaitForInputKeyPress AbilityTask !"), *Ability->GetName()); ClientProgressTask(); return; } - if (Ability->AbilityInputAction != nullptr) + UEnhancedInputComponent* EnhancedInputComponent = GetEnhancedInputComponent(); + + if (Ability->AbilityInputAction != nullptr && InputComponent != nullptr) { - UEnhancedInputComponent* const InputComponent = GetEnhancedInputComponent(); + const FEnhancedInputActionEventBinding& Binding = EnhancedInputComponent->BindAction( + Ability->AbilityInputAction, ETriggerEvent::Started, this, + &UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed); + + InputBindingHandle = Binding.GetHandle(); - if (InputComponent) + // Check if button was held when entering the task + if (bShouldCheckForPressDuringActivation) { - const FEnhancedInputActionEventBinding& Binding = InputComponent->BindAction( - Ability->AbilityInputAction, ETriggerEvent::Started, this, - &UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed); - - InputBindingHandle = Binding.GetHandle(); + FInputActionValue ActionValue = FInputActionValue(); + APlayerController* PC = AbilitySystemComponent->GetOwner()->GetInstigatorController(); + if (UEnhancedInputLocalPlayerSubsystem* InputSubSystem = ULocalPlayer::GetSubsystem(PC->GetLocalPlayer())) { + ActionValue = InputSubSystem->GetPlayerInput() ? InputSubSystem->GetPlayerInput()->GetActionValue(Ability->AbilityInputAction) : FInputActionValue(); + } + if (ActionValue.GetMagnitude() == 1) + { + InputBindingHandle = -1; + ClientProgressTask(); + } } } else @@ -39,10 +56,25 @@ void UGMCAbilityTask_WaitForInputKeyPress::Activate() } } +void UGMCAbilityTask_WaitForInputKeyPress::AncillaryTick(float DeltaTime) +{ + Super::AncillaryTick(DeltaTime); + if (bTaskCompleted) return; + + Duration = AbilitySystemComponent->ActionTimer - StartTime; + OnTick.Broadcast(Duration); + + if (MaxDuration > 0 && Duration >= MaxDuration) + { + ClientProgressTask(); + bTimedOut = true; + } +} + void UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed(const FInputActionValue& InputActionValue) { // Unbind from the input component so we don't fire multiple times. - if (UEnhancedInputComponent* InputComponent = GetValid(GetEnhancedInputComponent())) + if (InputComponent) { InputComponent->RemoveActionBindingForHandle(InputBindingHandle); InputBindingHandle = -1; @@ -51,9 +83,9 @@ void UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed(const FInputActionValue& ClientProgressTask(); } -UEnhancedInputComponent* UGMCAbilityTask_WaitForInputKeyPress::GetEnhancedInputComponent() const +UEnhancedInputComponent* UGMCAbilityTask_WaitForInputKeyPress::GetEnhancedInputComponent() { - UInputComponent* InputComponent = Ability->OwnerAbilityComponent->GetOwner()->GetComponentByClass(); + InputComponent = Ability->OwnerAbilityComponent->GetOwner()->GetComponentByClass(); if (InputComponent) { if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked(InputComponent)) @@ -67,7 +99,15 @@ UEnhancedInputComponent* UGMCAbilityTask_WaitForInputKeyPress::GetEnhancedInputC void UGMCAbilityTask_WaitForInputKeyPress::OnTaskCompleted() { EndTask(); - Completed.Broadcast(); + Duration = AbilitySystemComponent->ActionTimer - StartTime; + if (!bTimedOut) + { + Completed.Broadcast(Duration); + } + else + { + TimedOut.Broadcast(Duration); + } bTaskCompleted = true; } @@ -78,7 +118,7 @@ void UGMCAbilityTask_WaitForInputKeyPress::OnDestroy(bool bInOwnerFinished) // If we're still bound to the input component for some reason, we'll want to unbind. if (InputBindingHandle != -1) { - if (UEnhancedInputComponent* InputComponent = GetValid(GetEnhancedInputComponent())) + if (InputComponent) { InputComponent->RemoveActionBindingForHandle(InputBindingHandle); InputBindingHandle = -1; diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp index 1c731eb3..4919177e 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyRelease.cpp @@ -57,7 +57,7 @@ void UGMCAbilityTask_WaitForInputKeyRelease::Activate() void UGMCAbilityTask_WaitForInputKeyRelease::AncillaryTick(float DeltaTime) { - Super::Tick(DeltaTime); + Super::AncillaryTick(DeltaTime); if (bTaskCompleted) return; Duration = AbilitySystemComponent->ActionTimer - StartTime; @@ -66,6 +66,7 @@ void UGMCAbilityTask_WaitForInputKeyRelease::AncillaryTick(float DeltaTime) if (MaxDuration > 0 && Duration >= MaxDuration) { ClientProgressTask(); + bTimedOut = true; } } @@ -93,7 +94,14 @@ void UGMCAbilityTask_WaitForInputKeyRelease::OnTaskCompleted() { EndTask(); Duration = AbilitySystemComponent->ActionTimer - StartTime; - Completed.Broadcast(Duration); + if (!bTimedOut) + { + Completed.Broadcast(Duration); + } + else + { + TimedOut.Broadcast(Duration); + } bTaskCompleted = true; } diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h index 0847de03..93906636 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h @@ -8,7 +8,7 @@ #include "LatentActions.h" #include "WaitForInputKeyPress.generated.h" -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAbilityTaskWaitForInputKeyPress); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAbilityTaskWaitForInputKeyPress, float, Duration); /** * @@ -26,21 +26,42 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyPress : public UGMCAbi virtual void ClientProgressTask() override; UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Press",Category = "GMCAbilitySystem/Tasks") - static UGMCAbilityTask_WaitForInputKeyPress* WaitForKeyPress(UGMCAbility* OwningAbility); + static UGMCAbilityTask_WaitForInputKeyPress* WaitForKeyPress(UGMCAbility* OwningAbility,bool bCheckForReleaseDuringActivation = true, float MaxDuration = 0.0f); //Overriding BP async action base virtual void Activate() override; + + virtual void AncillaryTick(float DeltaTime) override; UPROPERTY(BlueprintAssignable) FAbilityTaskWaitForInputKeyPress Completed; + UPROPERTY(BlueprintAssignable) + FAbilityTaskWaitForInputKeyPress OnTick; + + // Called when duration goes over MaxDuration + UPROPERTY(BlueprintAssignable) + FAbilityTaskWaitForInputKeyPress TimedOut; + protected: void OnKeyPressed(const FInputActionValue& InputActionValue); + /** If true, we may complete this task during activation if the ability's input action key is already released. */ + UPROPERTY(Transient) + bool bShouldCheckForPressDuringActivation = true; + private: - UEnhancedInputComponent* GetEnhancedInputComponent() const; + UEnhancedInputComponent* GetEnhancedInputComponent(); int64 InputBindingHandle = -1; + + float MaxDuration; + float StartTime; + float Duration; + bool bTimedOut; + + UPROPERTY() + UInputComponent* InputComponent; }; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h index 59415f69..c40e9550 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h @@ -42,6 +42,10 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA UPROPERTY(BlueprintAssignable) FGMCAbilityTaskWaitForInputKeyRelease OnTick; + // Called when duration goes over MaxDuration + UPROPERTY(BlueprintAssignable) + FGMCAbilityTaskWaitForInputKeyRelease TimedOut; + protected: void OnKeyReleased(const FInputActionValue& InputActionValue); @@ -61,5 +65,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA float StartTime; float Duration; double OldTime; + + bool bTimedOut = false; }; \ No newline at end of file From b651e0a9166a7f3c103502e0ea07f1a9cb2c4ddd Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Tue, 6 May 2025 14:54:26 -0700 Subject: [PATCH 49/63] feat: Add spawning of Niagara Systems and Sounds from the ASC --- .../GMCAbilitySystem.Build.cs | 2 +- .../Components/GMCAbilityComponent.cpp | 84 ++++++++++++++++++- .../Public/Components/GMCAbilityComponent.h | 25 +++++- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs b/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs index 3b94ee9d..520b0451 100644 --- a/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs +++ b/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs @@ -30,7 +30,7 @@ public GMCAbilitySystem(ReadOnlyTargetRules Target) : base(Target) "CoreUObject", "Engine", "Slate", - "SlateCore", + "SlateCore", "Niagara" // ... add private dependencies that you statically link with here ... } ); diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index c26198f9..c18b77fc 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -6,10 +6,13 @@ #include "GMCAbilitySystem.h" #include "GMCOrganicMovementComponent.h" #include "GMCPlayerController.h" +#include "NiagaraFunctionLibrary.h" +#include "NiagaraFunctionLibrary.h" #include "Ability/GMCAbility.h" #include "Ability/GMCAbilityMapData.h" #include "Attributes/GMCAttributesData.h" #include "Effects/GMCAbilityEffect.h" +#include "Kismet/GameplayStatics.h" #include "Kismet/KismetSystemLibrary.h" #include "Net/UnrealNetwork.h" @@ -954,12 +957,14 @@ void UGMC_AbilitySystemComponent::ClientHandlePredictedPendingEffect() } } -void UGMC_AbilitySystemComponent::RPCTaskHeartbeat_Implementation(int AbilityID, int TaskID) +bool UGMC_AbilitySystemComponent::IsLocallyControlledPawnASC() const { - if (ActiveAbilities.Contains(AbilityID) && ActiveAbilities[AbilityID] != nullptr) + if (const APawn* Pawn = Cast(GetOwner())) { - ActiveAbilities[AbilityID]->HandleTaskHeartbeat(TaskID); + return Pawn->IsLocallyControlled(); } + + return false; } void UGMC_AbilitySystemComponent::RPCClientEndEffect_Implementation(int EffectID) @@ -971,6 +976,14 @@ void UGMC_AbilitySystemComponent::RPCClientEndEffect_Implementation(int EffectID } } +void UGMC_AbilitySystemComponent::RPCTaskHeartbeat_Implementation(int AbilityID, int TaskID) +{ + if (ActiveAbilities.Contains(AbilityID) && ActiveAbilities[AbilityID] != nullptr) + { + ActiveAbilities[AbilityID]->HandleTaskHeartbeat(TaskID); + } +} + void UGMC_AbilitySystemComponent::RPCClientEndAbility_Implementation(int AbilityID) { if (ActiveAbilities.Contains(AbilityID)) @@ -2187,6 +2200,71 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi } } } + +//////////////// FX + +UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, + bool bIsClientPredicted) +{ + if (SpawnParams.SystemTemplate == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Trying to spawn FX, but FX is null!")); + return nullptr; + } + + if (SpawnParams.WorldContextObject == nullptr) + { + SpawnParams.WorldContextObject = GetWorld(); + } + + if (HasAuthority()) + { + MC_SpawnParticleSystem(SpawnParams, bIsClientPredicted); + } + + UNiagaraComponent* SpawnedComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocationWithParams(SpawnParams); + return SpawnedComponent; +} + +void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted) +{ + // Server already spawned + if (HasAuthority()) return; + + // Owning client already spawned + if (IsLocallyControlledPawnASC() && bIsClientPredicted) return; + + SpawnParticleSystem(SpawnParams, bIsClientPredicted); +} + +void UGMC_AbilitySystemComponent::SpawnSound(USoundBase* Sound, float VolumeMultiplier, float PitchMultiplier, bool bIsClientPredicted) +{ + // Spawn sound + if (Sound == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Trying to spawn sound, but sound is null!")); + return; + } + + if (HasAuthority()) + { + MC_SpawnSound(Sound, VolumeMultiplier, PitchMultiplier, bIsClientPredicted); + } + + // Spawn Sound At Location + UGameplayStatics::PlaySoundAtLocation(GetWorld(), Sound, GetOwner()->GetActorLocation(), VolumeMultiplier, PitchMultiplier); +} + +void UGMC_AbilitySystemComponent::MC_SpawnSound_Implementation(USoundBase* Sound, float VolumeMultiplier, float PitchMultiplier, + bool bIsClientPredicted) +{ + // Server already spawned + if (HasAuthority()) return; + + if (IsLocallyControlledPawnASC() && bIsClientPredicted) return; + SpawnSound(Sound, VolumeMultiplier, PitchMultiplier, bIsClientPredicted); +} + // ReplicatedProps void UGMC_AbilitySystemComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const { diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index eb7f148c..e0570ee7 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -16,6 +16,9 @@ #include "GMCAbilityComponent.generated.h" +class UNiagaraComponent; +struct FFXSystemSpawnParameters; +class UNiagaraSystem; class UGMCAbilityAnimInstance; class UGMCAbilityMapData; class UGMCAttributesData; @@ -732,5 +735,25 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo void RPCClientEndEffect(int EffectID); friend UGMCAbilityAnimInstance; - + + // Networked FX + + // Is this ASC locally controlled? + bool IsLocallyControlledPawnASC() const; + + // Spawn a Niagara system at the given location + UFUNCTION(BlueprintCallable, Category="GMAS|FX") + UNiagaraComponent* SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted = false); + + UFUNCTION(NetMulticast, Unreliable) + void MC_SpawnParticleSystem(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted = false); + + // Spawn a Sound at the given location + UFUNCTION(BlueprintCallable, Category="GMAS|FX") + void SpawnSound(USoundBase* Sound, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); + + UFUNCTION(NetMulticast, Unreliable) + void MC_SpawnSound(USoundBase* Sound, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); + + }; From 0217fe9c569135be7324a3de100657f8c3ce04a0 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 7 May 2025 05:35:47 -0700 Subject: [PATCH 50/63] Add option for sim proxies to delay particle spawning by GMC smoothing delay --- .../Components/GMCAbilityComponent.cpp | 25 +++++++++++++++---- .../Public/Components/GMCAbilityComponent.h | 8 +++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index c18b77fc..56a401e1 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -2204,7 +2204,7 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi //////////////// FX UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, - bool bIsClientPredicted) + bool bIsClientPredicted, bool bDelayByGMCSmoothing) { if (SpawnParams.SystemTemplate == nullptr) { @@ -2219,14 +2219,29 @@ UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpa if (HasAuthority()) { - MC_SpawnParticleSystem(SpawnParams, bIsClientPredicted); + MC_SpawnParticleSystem(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); + } + + // Sim Proxies can delay FX by the smoothing delay to better line up + if (bDelayByGMCSmoothing && !HasAuthority() && !IsLocallyControlledPawnASC()) + { + float Delay = GMCMovementComponent->GetTime() - GMCMovementComponent->GetSmoothingTime(); + FTimerHandle DelayHandle; + GetWorld()->GetTimerManager().SetTimer(DelayHandle, [this, SpawnParams]() + { + UNiagaraFunctionLibrary::SpawnSystemAtLocationWithParams(SpawnParams); + }, Delay, false); + + UE_LOG(LogTemp, Warning, TEXT("Delay: %f"), Delay); + + return nullptr; } - UNiagaraComponent* SpawnedComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocationWithParams(SpawnParams); + UNiagaraComponent* SpawnedComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocationWithParams(SpawnParams); return SpawnedComponent; } -void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted) +void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted, bool bDelayByGMCSmoothing) { // Server already spawned if (HasAuthority()) return; @@ -2234,7 +2249,7 @@ void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FF // Owning client already spawned if (IsLocallyControlledPawnASC() && bIsClientPredicted) return; - SpawnParticleSystem(SpawnParams, bIsClientPredicted); + SpawnParticleSystem(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); } void UGMC_AbilitySystemComponent::SpawnSound(USoundBase* Sound, float VolumeMultiplier, float PitchMultiplier, bool bIsClientPredicted) diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index e0570ee7..ad0f2bb9 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -741,12 +741,14 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Is this ASC locally controlled? bool IsLocallyControlledPawnASC() const; - // Spawn a Niagara system at the given location + // Spawn a Niagara system + // IsClientPredicted: If true, the system will be spawned on the client immediately. False, the local client will spawn it when the multicast is received + // bDelayByGMCSmoothing: If true, the system will be spawned with a delay for SimProxies to match the smoothing delay UFUNCTION(BlueprintCallable, Category="GMAS|FX") - UNiagaraComponent* SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted = false); + UNiagaraComponent* SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); UFUNCTION(NetMulticast, Unreliable) - void MC_SpawnParticleSystem(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted = false); + void MC_SpawnParticleSystem(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); // Spawn a Sound at the given location UFUNCTION(BlueprintCallable, Category="GMAS|FX") From cbb5e0a048ee030a7d922a30b0397590aa479320 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 7 May 2025 05:59:03 -0700 Subject: [PATCH 51/63] Add WaitForInputKeyPressParameterized and Cancel Abilities by a group of Tags (nas) (#118) * Parameterized Input Key Press Task (#117) * - Added Parameterized Input Key Press Task * 5/6 * Give the Key Task OnTick and Timeout pins --------- Co-authored-by: NarsBars <150587668+NarsBars@users.noreply.github.com> --- .../Private/Ability/GMCAbility.cpp | 22 +++ .../WaitForInputKeyPressParameterized.cpp | 150 ++++++++++++++++++ .../Private/Effects/GMCAbilityEffect.cpp | 19 +++ .../Public/Ability/GMCAbility.h | 4 + .../Ability/Tasks/WaitForInputKeyPress.h | 4 +- .../Tasks/WaitForInputKeyPressParameterized.h | 70 ++++++++ .../Public/Effects/GMCAbilityEffect.h | 6 +- 7 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPressParameterized.cpp create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 9ec0413b..221f6f6c 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -188,8 +188,30 @@ void UGMCAbility::CancelAbilities() UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s has been cancelled by (tag) %s"), *AbilityTag.ToString(), *AbilityToCancelTag.ToString()); } } + // Cancel by AbilityDefinition (generic category tags) + TArray DefinitionTags; + AbilityDefinition.GetGameplayTagArray(DefinitionTags); + + for (const FGameplayTag& DefinitionTag : DefinitionTags) + { + // Prevent self-cancellation + if (AbilityTag.MatchesTag(DefinitionTag)) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability (tag) %s attempted to cancel itself via AbilityDefinition tag %s"), + *AbilityTag.ToString(), *DefinitionTag.ToString()); + continue; + } + + int Cancelled = OwnerAbilityComponent->EndAbilitiesByTag(DefinitionTag); + if (Cancelled > 0) + { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s cancelled %d ability(ies) by AbilityDefinition tag: %s"), + *AbilityTag.ToString(), Cancelled, *DefinitionTag.ToString()); + } + } } + void UGMCAbility::ServerConfirm() { bServerConfirmed = true; diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPressParameterized.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPressParameterized.cpp new file mode 100644 index 00000000..60bd63d5 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPressParameterized.cpp @@ -0,0 +1,150 @@ +#include "Ability/Tasks/WaitForInputKeyPressParameterized.h" + +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "Components/GMCAbilityComponent.h" + +UGMCAbilityTask_WaitForInputKeyPressParameterized* UGMCAbilityTask_WaitForInputKeyPressParameterized::WaitForKeyPress(UGMCAbility* OwningAbility, UInputAction* InputAction, bool bCheckForPressDuringActivation, float MaxDuration) +{ + UGMCAbilityTask_WaitForInputKeyPressParameterized* Task = NewAbilityTask(OwningAbility); + Task->Ability = OwningAbility; + Task->InputActionToWaitFor = InputAction; + Task->MaxDuration = MaxDuration; + Task->bShouldCheckForPressDuringActivation = bCheckForPressDuringActivation; + return Task; +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::Activate() +{ + Super::Activate(); + + StartTime = AbilitySystemComponent->ActionTimer; + + if (Ability->bAllowMultipleInstances) { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability %s is set to allow multiple instances and this should not be used with WaitForInputKeyPress AbilityTask !"), *Ability->GetName()); + ClientProgressTask(); + return; + } + + if (Ability->AbilityInputAction != nullptr) + { + UEnhancedInputComponent* const EnhInputComponent = GetEnhancedInputComponent(); + + if (InputComponent) + { + const FEnhancedInputActionEventBinding& Binding = EnhInputComponent->BindAction( + InputActionToWaitFor, ETriggerEvent::Started, this, + &UGMCAbilityTask_WaitForInputKeyPressParameterized::OnKeyPressed); + + InputBindingHandle = Binding.GetHandle(); + + // Check if button was held when entering the task + if (bShouldCheckForPressDuringActivation) + { + FInputActionValue ActionValue = FInputActionValue(); + APlayerController* PC = AbilitySystemComponent->GetOwner()->GetInstigatorController(); + if (UEnhancedInputLocalPlayerSubsystem* InputSubSystem = ULocalPlayer::GetSubsystem(PC->GetLocalPlayer())) { + ActionValue = InputSubSystem->GetPlayerInput() ? InputSubSystem->GetPlayerInput()->GetActionValue(Ability->AbilityInputAction) : FInputActionValue(); + } + if (ActionValue.GetMagnitude() == 1) + { + InputBindingHandle = -1; + ClientProgressTask(); + } + } + } + } + else + { + ClientProgressTask(); + } +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::AncillaryTick(float DeltaTime) +{ + Super::AncillaryTick(DeltaTime); + + if (bTaskCompleted) return; + + Duration = AbilitySystemComponent->ActionTimer - StartTime; + OnTick.Broadcast(Duration); + + if (MaxDuration > 0 && Duration >= MaxDuration) + { + ClientProgressTask(); + bTimedOut = true; + } + +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::OnKeyPressed(const FInputActionValue& InputActionValue) +{ + // Unbind from the input component so we don't fire multiple times. + if (UEnhancedInputComponent* EnhInputComponent = GetValid(GetEnhancedInputComponent())) + { + EnhInputComponent->RemoveActionBindingForHandle(InputBindingHandle); + InputBindingHandle = -1; + } + + ClientProgressTask(); +} + +UEnhancedInputComponent* UGMCAbilityTask_WaitForInputKeyPressParameterized::GetEnhancedInputComponent() +{ + InputComponent = Ability->OwnerAbilityComponent->GetOwner()->GetComponentByClass(); + if (InputComponent) + { + if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked(InputComponent)) + { + return EnhancedInputComponent; + } + } + return nullptr; +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::OnTaskCompleted() +{ + EndTask(); + Duration = AbilitySystemComponent->ActionTimer - StartTime; + if (!bTimedOut) + { + Completed.Broadcast(Duration); + } + else + { + TimedOut.Broadcast(Duration); + } + bTaskCompleted = true; +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::OnDestroy(bool bInOwnerFinished) +{ + Super::OnDestroy(bInOwnerFinished); + + // If we're still bound to the input component for some reason, we'll want to unbind. + if (InputBindingHandle != -1) + { + if (InputComponent) + { + InputComponent->RemoveActionBindingForHandle(InputBindingHandle); + InputBindingHandle = -1; + } + } +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::ProgressTask(FInstancedStruct& TaskData) +{ + Super::ProgressTask(TaskData); + OnTaskCompleted(); +} + +void UGMCAbilityTask_WaitForInputKeyPressParameterized::ClientProgressTask() +{ + FGMCAbilityTaskData TaskData; + TaskData.TaskType = EGMCAbilityTaskDataType::Progress; + TaskData.AbilityID = Ability->GetAbilityID(); + TaskData.TaskID = TaskID; + const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData); + + Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance); +} diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index bf1aa49e..bbb3cfd9 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -68,6 +68,7 @@ void UGMCAbilityEffect::StartEffect() AddTagsToOwner(); AddAbilitiesToOwner(); EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnActivation); + CancelAbilitiesByDefinition(); bHasAppliedEffect = true; @@ -347,3 +348,21 @@ void UGMCAbilityEffect::CheckState() default: break; } } + +void UGMCAbilityEffect::CancelAbilitiesByDefinition() +{ + if (!OwnerAbilityComponent) return; + + TArray DefinitionTags; + EffectData.EffectDefinition.GetGameplayTagArray(DefinitionTags); + + for (const FGameplayTag& Tag : DefinitionTags) + { + const int NumCancelled = OwnerAbilityComponent->EndAbilitiesByTag(Tag); + if (NumCancelled > 0) + { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Effect (tag: %s) cancelled %d ability(ies) via EffectDefinition tag: %s"), + *EffectData.EffectTag.ToString(), NumCancelled, *Tag.ToString()); + } + } +} diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index 690e4f37..0fa264b7 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -117,6 +117,10 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UPROPERTY(EditAnywhere, meta=(Categories="Ability"), Category = "GMCAbilitySystem") FGameplayTag AbilityTag; + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(Categories="Ability")) + // Container for a more generalized definition of abilities + FGameplayTagContainer AbilityDefinition; + // An Effect that modifies attributes when the ability is activated UPROPERTY(EditAnywhere, Category = "GMCAbilitySystem") TSubclassOf AbilityCost; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h index 93906636..6ac66f94 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h @@ -26,7 +26,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyPress : public UGMCAbi virtual void ClientProgressTask() override; UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Press",Category = "GMCAbilitySystem/Tasks") - static UGMCAbilityTask_WaitForInputKeyPress* WaitForKeyPress(UGMCAbility* OwningAbility,bool bCheckForReleaseDuringActivation = true, float MaxDuration = 0.0f); + static UGMCAbilityTask_WaitForInputKeyPress* WaitForKeyPress(UGMCAbility* OwningAbility, bool bCheckForPressDuringActivation = true, float MaxDuration = 0.0f); //Overriding BP async action base virtual void Activate() override; @@ -49,7 +49,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyPress : public UGMCAbi /** If true, we may complete this task during activation if the ability's input action key is already released. */ UPROPERTY(Transient) - bool bShouldCheckForPressDuringActivation = true; + bool bShouldCheckForPressDuringActivation = false; private: diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h new file mode 100644 index 00000000..2bc08cd0 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h @@ -0,0 +1,70 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Ability/Tasks/GMCAbilityTaskBase.h" +#include "EnhancedInputComponent.h" +#include "Ability/GMCAbility.h" +#include "InstancedStruct.h" +#include "LatentActions.h" +#include "WaitForInputKeyPressParameterized.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAbilityTaskWaitForInputKeyPressParameterized, float, Duration); + +/** + * + */ +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyPressParameterized : public UGMCAbilityTaskBase { + GENERATED_BODY() + +public: + + void OnTaskCompleted(); + virtual void OnDestroy(bool bInOwnerFinished) override; + + virtual void ProgressTask(FInstancedStruct& TaskData) override; + virtual void ClientProgressTask() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility"), DisplayName = "Wait For Input Key Press Parameterized", Category = "GMCAbilitySystem/Tasks") + static UGMCAbilityTask_WaitForInputKeyPressParameterized* WaitForKeyPress(UGMCAbility* OwningAbility, UInputAction* InputAction, bool bCheckForPressDuringActivation = true, float MaxDuration = 0.0f); + + //Overriding BP async action base + virtual void Activate() override; + + virtual void AncillaryTick(float DeltaTime) override; + + UPROPERTY(BlueprintAssignable) + FAbilityTaskWaitForInputKeyPressParameterized Completed; + + UPROPERTY(BlueprintAssignable) + FAbilityTaskWaitForInputKeyPressParameterized OnTick; + + // Called when duration goes over MaxDuration + UPROPERTY(BlueprintAssignable) + FAbilityTaskWaitForInputKeyPressParameterized TimedOut; + + UPROPERTY() + UInputAction* InputActionToWaitFor = nullptr; + +protected: + + void OnKeyPressed(const FInputActionValue& InputActionValue); + + /** If true, we may complete this task during activation if the ability's input action key is already released. */ + UPROPERTY(Transient) + bool bShouldCheckForPressDuringActivation = false; + +private: + + UEnhancedInputComponent* GetEnhancedInputComponent(); + + int64 InputBindingHandle = -1; + + float MaxDuration; + float StartTime; + float Duration; + bool bTimedOut; + + UPROPERTY() + UInputComponent* InputComponent; +}; diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index cc4c3c2a..57c96019 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -106,6 +106,10 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTag EffectTag; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + // Container for a more generalized definition of effects + FGameplayTagContainer EffectDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedTags; @@ -258,7 +262,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool DuplicateEffectAlreadyApplied(); - + void CancelAbilitiesByDefinition(); public: From d315cdf1b738b28860df6ff99357f2c3e924d42c Mon Sep 17 00:00:00 2001 From: Baja Short Long Date: Wed, 7 May 2025 05:59:34 -0700 Subject: [PATCH 52/63] Bug fixes for WaitForGameplayTagChange Task (#112) * Filter SmoothedListenServerPawn from checking tag and attributes on GenSimulationTick * Fix typo where Unset tag change checks AddedTags instead of RemoveTags --- .../Private/Ability/Tasks/WaitForGameplayTagChange.cpp | 2 +- .../Private/Components/GMCAbilityComponent.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGameplayTagChange.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGameplayTagChange.cpp index 58e4fe41..998c3fb0 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGameplayTagChange.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGameplayTagChange.cpp @@ -31,7 +31,7 @@ void UGMCAbilityTask_WaitForGameplayTagChange::OnGameplayTagChanged(const FGamep break; case Unset: - MatchedTags = AddedTags.Filter(Tags); + MatchedTags = RemovedTags.Filter(Tags); break; case Changed: diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 56a401e1..b447da92 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -560,8 +560,12 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) void UGMC_AbilitySystemComponent::GenSimulationTick(float DeltaTime) { - CheckActiveTagsChanged(); - CheckAttributeChanged(); + + if (!GMCMovementComponent->IsSmoothedListenServerPawn()) + { + CheckActiveTagsChanged(); + CheckAttributeChanged(); + } if (GMCMovementComponent->GetSmoothingTargetIdx() == -1) return; const FVector TargetLocation = GMCMovementComponent->MoveHistory[GMCMovementComponent->GetSmoothingTargetIdx()].OutputState.ActorLocation.Read(); From 1d1ff111413bcc6ba33c006541256188f163721a Mon Sep 17 00:00:00 2001 From: NarsBars <150587668+NarsBars@users.noreply.github.com> Date: Wed, 7 May 2025 09:16:14 -0700 Subject: [PATCH 53/63] definition tags query (#119) * - Added Parameterized Input Key Press Task * 5/6 * Update GMCAbilityComponent.cpp * Update GMCAbility.cpp * DefinitionTags * query fixed for effects * Update GMCAbility.cpp unnecessary warning --- .../Private/Ability/GMCAbility.cpp | 29 ++++++++++--------- .../Components/GMCAbilityComponent.cpp | 21 +++++++++++++- .../Private/Effects/GMCAbilityEffect.cpp | 21 ++++++-------- .../Public/Ability/GMCAbility.h | 2 +- .../Public/Components/GMCAbilityComponent.h | 4 +++ 5 files changed, 50 insertions(+), 27 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 221f6f6c..6c41bcc7 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -188,25 +188,28 @@ void UGMCAbility::CancelAbilities() UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s has been cancelled by (tag) %s"), *AbilityTag.ToString(), *AbilityToCancelTag.ToString()); } } - // Cancel by AbilityDefinition (generic category tags) - TArray DefinitionTags; - AbilityDefinition.GetGameplayTagArray(DefinitionTags); + + FGameplayTagContainer TagsToMatch = CancelAbilitiesWithTag; + TagsToMatch.AppendTags(AbilityDefinition); + + if (TagsToMatch.Num() == 0) return; // do nothing - for (const FGameplayTag& DefinitionTag : DefinitionTags) + // should prob also append AbilityTag if want to use this system but left alone for now just in case + FGameplayTagQuery CancelQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(TagsToMatch); + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Generated Cancel Query: %s"), *CancelQuery.GetDescription()); + + for (const auto& ActiveAbility : OwnerAbilityComponent->GetActiveAbilities()) { - // Prevent self-cancellation - if (AbilityTag.MatchesTag(DefinitionTag)) + if (ActiveAbility.Value == this) { - UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability (tag) %s attempted to cancel itself via AbilityDefinition tag %s"), - *AbilityTag.ToString(), *DefinitionTag.ToString()); - continue; + continue; // prevent self-cancellation } - int Cancelled = OwnerAbilityComponent->EndAbilitiesByTag(DefinitionTag); - if (Cancelled > 0) + if (CancelQuery.Matches(ActiveAbility.Value->AbilityDefinition)) { - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s cancelled %d ability(ies) by AbilityDefinition tag: %s"), - *AbilityTag.ToString(), Cancelled, *DefinitionTag.ToString()); + ActiveAbility.Value->SetPendingEnd(); + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability %s cancelled ability %s (matching query)"), + *AbilityTag.ToString(), *ActiveAbility.Value->AbilityTag.ToString()); } } } diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index b447da92..217ec552 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -7,7 +7,6 @@ #include "GMCOrganicMovementComponent.h" #include "GMCPlayerController.h" #include "NiagaraFunctionLibrary.h" -#include "NiagaraFunctionLibrary.h" #include "Ability/GMCAbility.h" #include "Ability/GMCAbilityMapData.h" #include "Attributes/GMCAttributesData.h" @@ -447,6 +446,26 @@ int UGMC_AbilitySystemComponent::EndAbilitiesByClass(TSubclassOf Ab } +int UGMC_AbilitySystemComponent::EndAbilitiesByQuery(const FGameplayTagQuery& Query) +{ + int AbilitiesEnded = 0; + + for (const auto& Pair : ActiveAbilities) + { + if (UGMCAbility* Ability = Pair.Value) + { + if (Query.Matches(Ability->AbilityDefinition)) + { + Ability->SetPendingEnd(); + AbilitiesEnded++; + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("cancelled ability %s by query"), *Ability->AbilityTag.ToString()); + } + } + } + return AbilitiesEnded; +} + + int32 UGMC_AbilitySystemComponent::GetActiveAbilityCountByTag(FGameplayTag AbilityTag) { int32 Result = 0; diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index bbb3cfd9..811dccd2 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -351,18 +351,15 @@ void UGMCAbilityEffect::CheckState() void UGMCAbilityEffect::CancelAbilitiesByDefinition() { - if (!OwnerAbilityComponent) return; + if (!OwnerAbilityComponent) return; - TArray DefinitionTags; - EffectData.EffectDefinition.GetGameplayTagArray(DefinitionTags); + // build query here + FGameplayTagContainer TagsToMatch = EffectData.EffectDefinition; + if (TagsToMatch.Num() == 0) return; - for (const FGameplayTag& Tag : DefinitionTags) - { - const int NumCancelled = OwnerAbilityComponent->EndAbilitiesByTag(Tag); - if (NumCancelled > 0) - { - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Effect (tag: %s) cancelled %d ability(ies) via EffectDefinition tag: %s"), - *EffectData.EffectTag.ToString(), NumCancelled, *Tag.ToString()); - } - } + FGameplayTagQuery CancelQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(TagsToMatch); + int NumCancelled = OwnerAbilityComponent->EndAbilitiesByQuery(CancelQuery); + + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Effect %s cancelled %d ability(ies) via EffectDefinition query."), + *EffectData.EffectTag.ToString(), NumCancelled); } diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index 0fa264b7..bea9f4bd 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -117,7 +117,7 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UPROPERTY(EditAnywhere, meta=(Categories="Ability"), Category = "GMCAbilitySystem") FGameplayTag AbilityTag; - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(Categories="Ability")) + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") // Container for a more generalized definition of abilities FGameplayTagContainer AbilityDefinition; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index ad0f2bb9..da894513 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -218,6 +218,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // End all abilities with the corresponding tag, returns the number of abilities ended int EndAbilitiesByClass(TSubclassOf AbilityClass); + UFUNCTION(BlueprintCallable, DisplayName = "End Abilities (By Definition Query)", Category="GMAS|Abilities") + // End all abilities with defintions matching query + int EndAbilitiesByQuery(const FGameplayTagQuery& Query); + UFUNCTION(BlueprintCallable, DisplayName="Count Activated Ability Instances (by tag)", Category="GMAS|Abilities") int32 GetActiveAbilityCountByTag(FGameplayTag AbilityTag); From 11c33c85b41cbd7caf6ff1d33eaf7433452623d1 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 7 May 2025 09:33:10 -0700 Subject: [PATCH 54/63] Fix OnAttributeChanged double calling --- .../Private/Components/GMCAbilityComponent.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index b447da92..d6034ff3 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -728,6 +728,7 @@ void UGMC_AbilitySystemComponent::CheckAttributeChanged() { FAttribute& OldAttribute = OldBoundAttributes.Attributes[i]; if (Attribute.Value != OldAttribute.Value) { + NativeAttributeChangeDelegate.Broadcast(Attribute.Tag, OldAttribute.Value, Attribute.Value); OnAttributeChanged.Broadcast(Attribute.Tag, OldAttribute.Value, Attribute.Value); OldAttribute.Value = Attribute.Value; } @@ -2194,9 +2195,6 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi // Only broadcast a change if we've genuinely changed. if (OldValue != AffectedAttribute->Value) { - OnAttributeChanged.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); - NativeAttributeChangeDelegate.Broadcast(AffectedAttribute->Tag, OldValue, AffectedAttribute->Value); - if (!AffectedAttribute->bIsGMCBound) { UnBoundAttributes.MarkAttributeDirty(*AffectedAttribute); From 37cbd13c97c296345429660c1c8f4c19c809a8a4 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 7 May 2025 15:10:29 -0700 Subject: [PATCH 55/63] Remove extra logging. Make SpawnSound and SpawnParticles public --- .../Private/Components/GMCAbilityComponent.cpp | 3 --- .../GMCAbilitySystem/Public/Components/GMCAbilityComponent.h | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 41cfca20..2bbd836f 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -2252,9 +2252,6 @@ UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpa { UNiagaraFunctionLibrary::SpawnSystemAtLocationWithParams(SpawnParams); }, Delay, false); - - UE_LOG(LogTemp, Warning, TEXT("Delay: %f"), Delay); - return nullptr; } diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index da894513..68cce4ed 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -740,11 +740,11 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo friend UGMCAbilityAnimInstance; +public: // Networked FX - // Is this ASC locally controlled? bool IsLocallyControlledPawnASC() const; - + // Spawn a Niagara system // IsClientPredicted: If true, the system will be spawned on the client immediately. False, the local client will spawn it when the multicast is received // bDelayByGMCSmoothing: If true, the system will be spawned with a delay for SimProxies to match the smoothing delay From 22f0a5035d168f324a617f6cfbec7e758947ca2b Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Wed, 7 May 2025 15:40:37 -0700 Subject: [PATCH 56/63] Add Location for SpawnSound --- .../Private/Components/GMCAbilityComponent.cpp | 10 +++++----- .../Public/Components/GMCAbilityComponent.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 2bbd836f..68321e93 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -2270,7 +2270,7 @@ void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FF SpawnParticleSystem(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); } -void UGMC_AbilitySystemComponent::SpawnSound(USoundBase* Sound, float VolumeMultiplier, float PitchMultiplier, bool bIsClientPredicted) +void UGMC_AbilitySystemComponent::SpawnSound(USoundBase* Sound, const FVector Location, const float VolumeMultiplier, const float PitchMultiplier, const bool bIsClientPredicted) { // Spawn sound if (Sound == nullptr) @@ -2281,21 +2281,21 @@ void UGMC_AbilitySystemComponent::SpawnSound(USoundBase* Sound, float VolumeMult if (HasAuthority()) { - MC_SpawnSound(Sound, VolumeMultiplier, PitchMultiplier, bIsClientPredicted); + MC_SpawnSound(Sound, Location, VolumeMultiplier, PitchMultiplier, bIsClientPredicted); } // Spawn Sound At Location - UGameplayStatics::PlaySoundAtLocation(GetWorld(), Sound, GetOwner()->GetActorLocation(), VolumeMultiplier, PitchMultiplier); + UGameplayStatics::PlaySoundAtLocation(GetWorld(), Sound, Location, VolumeMultiplier, PitchMultiplier); } -void UGMC_AbilitySystemComponent::MC_SpawnSound_Implementation(USoundBase* Sound, float VolumeMultiplier, float PitchMultiplier, +void UGMC_AbilitySystemComponent::MC_SpawnSound_Implementation(USoundBase* Sound, const FVector Location, const float VolumeMultiplier, const float PitchMultiplier, bool bIsClientPredicted) { // Server already spawned if (HasAuthority()) return; if (IsLocallyControlledPawnASC() && bIsClientPredicted) return; - SpawnSound(Sound, VolumeMultiplier, PitchMultiplier, bIsClientPredicted); + SpawnSound(Sound, Location, VolumeMultiplier, PitchMultiplier, bIsClientPredicted); } // ReplicatedProps diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 68cce4ed..5ff439c2 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -756,10 +756,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Spawn a Sound at the given location UFUNCTION(BlueprintCallable, Category="GMAS|FX") - void SpawnSound(USoundBase* Sound, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); + void SpawnSound(USoundBase* Sound, FVector Location, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); UFUNCTION(NetMulticast, Unreliable) - void MC_SpawnSound(USoundBase* Sound, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); + void MC_SpawnSound(USoundBase* Sound, FVector Location, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); }; From ebdc72713d9dc78f2cc3d539564ae70568004253 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Thu, 8 May 2025 06:52:58 -0700 Subject: [PATCH 57/63] Add CurrentMontage to GMCWaitForGMCMontageChange output pins --- .../Private/Ability/Tasks/WaitForGMCMontageChange.cpp | 4 ++-- .../Public/Ability/Tasks/WaitForGMCMontageChange.h | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp index 17911953..e374f93b 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForGMCMontageChange.cpp @@ -43,7 +43,7 @@ void UGMCAbilityTask_WaitForGMCMontageChange::Tick(float DeltaTime) { Super::Tick(DeltaTime); - const UAnimMontage* RunningMontage = OrganicMovementCmp->GetActiveMontage(OrganicMovementCmp->MontageTracker); + RunningMontage = OrganicMovementCmp->GetActiveMontage(OrganicMovementCmp->MontageTracker); // If the montage has changed, finish the task if (StartingMontage != RunningMontage) @@ -56,7 +56,7 @@ void UGMCAbilityTask_WaitForGMCMontageChange::OnFinish() { if (GetState() != EGameplayTaskState::Finished) { - Completed.Broadcast(); + Completed.Broadcast(RunningMontage); EndTask(); } } diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h index b934c765..60215789 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h @@ -3,7 +3,7 @@ #include "GMCOrganicMovementComponent.h" #include "WaitForGMCMontageChange.generated.h" -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGMCAbilityTaskWaitForGMCMontageChangeDelayOutputPin); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGMCAbilityTaskWaitForGMCMontageChangeDelayOutputPin, UAnimMontage*, CurrentMontage); UCLASS() class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForGMCMontageChange : public UGMCAbilityTaskBase @@ -31,4 +31,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForGMCMontageChange : public UGMC private: void OnFinish(); + + UPROPERTY() + UAnimMontage* RunningMontage; }; \ No newline at end of file From 4b9f95e9603e374d964bc57a9c204ecf315749a8 Mon Sep 17 00:00:00 2001 From: NarsBars <150587668+NarsBars@users.noreply.github.com> Date: Thu, 8 May 2025 06:58:18 -0700 Subject: [PATCH 58/63] remove effects by definition query (#120) * - Added Parameterized Input Key Press Task * 5/6 * Update GMCAbilityComponent.cpp * Update GMCAbility.cpp * DefinitionTags * query fixed for effects * Update GMCAbility.cpp unnecessary warning * Remove Effects By Definition Query --- .../Components/GMCAbilityComponent.cpp | 26 ++++++++++++++++--- .../Public/Components/GMCAbilityComponent.h | 7 +++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 68321e93..cd2a44f7 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Fill out your copyright notice in the Description page of Project Settings. #include "Components/GMCAbilityComponent.h" @@ -450,9 +450,9 @@ int UGMC_AbilitySystemComponent::EndAbilitiesByQuery(const FGameplayTagQuery& Qu { int AbilitiesEnded = 0; - for (const auto& Pair : ActiveAbilities) + for (const auto& ActiveAbilityData : ActiveAbilities) { - if (UGMCAbility* Ability = Pair.Value) + if (UGMCAbility* Ability = ActiveAbilityData.Value) { if (Query.Matches(Ability->AbilityDefinition)) { @@ -1867,7 +1867,6 @@ void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffectByTag(FGameplayTag Ta } - TArray UGMC_AbilitySystemComponent::EffectsMatchingTag(const FGameplayTag& Tag, int32 NumToRemove) const { if (NumToRemove < -1 || !Tag.IsValid()) { @@ -1926,6 +1925,25 @@ int32 UGMC_AbilitySystemComponent::RemoveEffectByTagSafe(FGameplayTag InEffectTa return EffectsToRemove.Num(); } +int UGMC_AbilitySystemComponent::RemoveEffectsByQuery(const FGameplayTagQuery& Query, EGMCAbilityEffectQueueType QueueType) +{ + int EffectsRemoved = 0; + + for (const auto& EffectEntry : ActiveEffects) + { + if (UGMCAbilityEffect* Effect = EffectEntry.Value) + { + if (Query.Matches(Effect->EffectData.EffectDefinition)) + { + RemoveActiveAbilityEffectSafe(Effect, QueueType); + EffectsRemoved++; + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Removed effect %s by query"), *Effect->EffectData.EffectTag.ToString()); + } + } + } + return EffectsRemoved; +} + bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbilityEffectQueueType QueueType) { if (!Ids.Num()) { diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 5ff439c2..33ef5cf7 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Fill out your copyright notice in the Description page of Project Settings. #pragma once @@ -219,7 +219,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo int EndAbilitiesByClass(TSubclassOf AbilityClass); UFUNCTION(BlueprintCallable, DisplayName = "End Abilities (By Definition Query)", Category="GMAS|Abilities") - // End all abilities with defintions matching query + // End all abilities matching query int EndAbilitiesByQuery(const FGameplayTagQuery& Query); UFUNCTION(BlueprintCallable, DisplayName="Count Activated Ability Instances (by tag)", Category="GMAS|Abilities") @@ -378,6 +378,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effect by Handle") bool RemoveEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType); + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effects by Definition Query") + int32 RemoveEffectsByQuery(const FGameplayTagQuery& Query, EGMCAbilityEffectQueueType QueueType); + /** * Gets the number of active effects with the inputted tag. * Returns -1 if tag is invalid. From 2c889b3e038ad17ff94b61f93f2b2356b42dcf2f Mon Sep 17 00:00:00 2001 From: NarsBars <150587668+NarsBars@users.noreply.github.com> Date: Thu, 8 May 2025 11:07:44 -0700 Subject: [PATCH 59/63] Tag queries for everything (#121) * - Added Parameterized Input Key Press Task * 5/6 * Update GMCAbilityComponent.cpp * Update GMCAbility.cpp * DefinitionTags * query fixed for effects * Update GMCAbility.cpp unnecessary warning * Remove Effects By Definition Query * queries queries queries queries for everything --- .../Private/Ability/GMCAbility.cpp | 112 ++++++++++-------- .../Components/GMCAbilityComponent.cpp | 17 ++- .../Private/Effects/GMCAbilityEffect.cpp | 39 ++++-- .../Public/Ability/GMCAbility.h | 27 ++++- .../Public/Components/GMCAbilityComponent.h | 11 +- .../Public/Effects/GMCAbilityEffect.h | 34 +++++- 6 files changed, 171 insertions(+), 69 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 6c41bcc7..54203027 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -12,7 +12,7 @@ UWorld* UGMCAbility::GetWorld() const // UObject::ImplementsGetWorld(), which just blithely and blindly calls GetWorld(). return nullptr; } - + #if WITH_EDITOR if (GIsEditor) { @@ -25,9 +25,9 @@ UWorld* UGMCAbility::GetWorld() const if (Contexts.Num() == 0) { UE_LOG(LogGMCAbilitySystem, Error, TEXT("%s: instanciated class with no valid world!"), *GetClass()->GetName()) - return nullptr; + return nullptr; } - + return Contexts[0].World(); } @@ -35,7 +35,7 @@ void UGMCAbility::Tick(float DeltaTime) { // Don't tick before the ability is initialized or after it has ended if (AbilityState == EAbilityState::PreExecution || AbilityState == EAbilityState::Ended) return; - + if (!OwnerAbilityComponent->HasAuthority()) { if (!bServerConfirmed && ClientStartTime + ServerConfirmTimeout < OwnerAbilityComponent->ActionTimer) @@ -50,34 +50,34 @@ void UGMCAbility::Tick(float DeltaTime) EndAbility(); return; } - + TickTasks(DeltaTime); TickEvent(DeltaTime); } -void UGMCAbility::AncillaryTick(float DeltaTime){ +void UGMCAbility::AncillaryTick(float DeltaTime) { // Don't tick before the ability is initialized or after it has ended if (AbilityState == EAbilityState::PreExecution || AbilityState == EAbilityState::Ended) return; - + AncillaryTickTasks(DeltaTime); AncillaryTickEvent(DeltaTime); } void UGMCAbility::TickTasks(float DeltaTime) { - for (int i=0; i < RunningTasks.Num(); i++) + for (int i = 0; i < RunningTasks.Num(); i++) { UGMCAbilityTaskBase* Task = RunningTasks[i]; - if (Task == nullptr) {continue;} + if (Task == nullptr) { continue; } Task->Tick(DeltaTime); } } -void UGMCAbility::AncillaryTickTasks(float DeltaTime){ - for (int i=0; i < RunningTasks.Num(); i++) +void UGMCAbility::AncillaryTickTasks(float DeltaTime) { + for (int i = 0; i < RunningTasks.Num(); i++) { UGMCAbilityTaskBase* Task = RunningTasks[i]; - if (Task == nullptr) {continue;} + if (Task == nullptr) { continue; } Task->AncillaryTick(DeltaTime); } } @@ -95,7 +95,7 @@ void UGMCAbility::Execute(UGMC_AbilitySystemComponent* InAbilityComponent, int I bool UGMCAbility::CanAffordAbilityCost() const { if (AbilityCost == nullptr || OwnerAbilityComponent == nullptr) return true; - + UGMCAbilityEffect* AbilityEffect = AbilityCost->GetDefaultObject(); for (FGMCAttributeModifier AttributeModifier : AbilityEffect->EffectData.Modifiers) { @@ -126,15 +126,15 @@ void UGMCAbility::CommitAbilityCooldown() void UGMCAbility::CommitAbilityCost() { if (AbilityCost == nullptr || OwnerAbilityComponent == nullptr) return; - + const UGMCAbilityEffect* EffectCDO = DuplicateObject(AbilityCost->GetDefaultObject(), this); FGMCAbilityEffectData EffectData = EffectCDO->EffectData; EffectData.OwnerAbilityComponent = OwnerAbilityComponent; AbilityCostInstance = OwnerAbilityComponent->ApplyAbilityEffect(DuplicateObject(EffectCDO, this), EffectData); } -void UGMCAbility::RemoveAbilityCost(){ - if(AbilityCostInstance){ +void UGMCAbility::RemoveAbilityCost() { + if (AbilityCostInstance) { OwnerAbilityComponent->RemoveActiveAbilityEffect(AbilityCostInstance); } } @@ -183,33 +183,24 @@ void UGMCAbility::CancelAbilities() UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Ability (tag) %s is trying to cancel itself, if you attempt to reset the ability, please use //TODO instead"), *AbilityTag.ToString()); continue; } - + if (OwnerAbilityComponent->EndAbilitiesByTag(AbilityToCancelTag)) { - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s has been cancelled by (tag) %s"), *AbilityTag.ToString(), *AbilityToCancelTag.ToString()); + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability (tag) %s has been cancelled by (tag) %s"), *AbilityTag.ToString(), *AbilityToCancelTag.ToString()); } } - - FGameplayTagContainer TagsToMatch = CancelAbilitiesWithTag; - TagsToMatch.AppendTags(AbilityDefinition); - if (TagsToMatch.Num() == 0) return; // do nothing - - // should prob also append AbilityTag if want to use this system but left alone for now just in case - FGameplayTagQuery CancelQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(TagsToMatch); - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Generated Cancel Query: %s"), *CancelQuery.GetDescription()); - - for (const auto& ActiveAbility : OwnerAbilityComponent->GetActiveAbilities()) + if (!EndOtherAbilitiesQuery.IsEmpty()) { - if (ActiveAbility.Value == this) + for (const auto& ActiveAbility : OwnerAbilityComponent->GetActiveAbilities()) { - continue; // prevent self-cancellation - } + if (ActiveAbility.Value == this) continue; - if (CancelQuery.Matches(ActiveAbility.Value->AbilityDefinition)) - { - ActiveAbility.Value->SetPendingEnd(); - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability %s cancelled ability %s (matching query)"), - *AbilityTag.ToString(), *ActiveAbility.Value->AbilityTag.ToString()); + if (EndOtherAbilitiesQuery.Matches(ActiveAbility.Value->AbilityDefinition)) + { + ActiveAbility.Value->SetPendingEnd(); + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability %s cancelled ability %s (matching definition query)"), + *AbilityTag.ToString(), *ActiveAbility.Value->AbilityTag.ToString()); + } } } } @@ -255,7 +246,7 @@ void UGMCAbility::OnGameplayTaskInitialized(UGameplayTask& Task) } AbilityTask->SetAbilitySystemComponent(OwnerAbilityComponent); AbilityTask->Ability = this; - + } void UGMCAbility::OnGameplayTaskActivated(UGameplayTask& Task) @@ -275,7 +266,7 @@ void UGMCAbility::FinishEndAbility() { if (Task.Value == nullptr) continue; Task.Value->EndTaskGMAS(); } - + AbilityState = EAbilityState::Ended; } @@ -315,21 +306,36 @@ bool UGMCAbility::PreBeginAbility() { void UGMCAbility::BeginAbility() { - + if (OwnerAbilityComponent->IsAbilityTagBlocked(AbilityTag)) { CancelAbility(); return; } - + if (!BlockOtherAbilitiesQuery.IsEmpty()) + { + FGameplayTagQuery BlockQuery = BlockOtherAbilitiesQuery; + for (auto& ActiveAbility : OwnerAbilityComponent->GetActiveAbilities()) + { + const FGameplayTagContainer& ActiveAbilityTags = ActiveAbility.Value->AbilityDefinition; + + if (BlockQuery.Matches(ActiveAbilityTags)) + { + ActiveAbility.Value->SetPendingEnd(); + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability %s blocked ability %s (matching query)"), + *AbilityTag.ToString(), *ActiveAbility.Value->AbilityTag.ToString()); + } + } + } + if (bApplyCooldownAtAbilityBegin) { CommitAbilityCooldown(); } - + // Initialize Ability AbilityState = EAbilityState::Initialized; - + // Cancel Abilities in CancelAbilitiesWithTag container CancelAbilities(); @@ -358,16 +364,16 @@ AActor* UGMCAbility::GetOwnerActor() const return OwnerAbilityComponent->GetOwner(); } -AGMC_Pawn* UGMCAbility::GetOwnerPawn() const{ - if (AGMC_Pawn* OwningPawn = Cast(GetOwnerActor())){ +AGMC_Pawn* UGMCAbility::GetOwnerPawn() const { + if (AGMC_Pawn* OwningPawn = Cast(GetOwnerActor())) { return OwningPawn; } return nullptr; } -AGMC_PlayerController* UGMCAbility::GetOwningPlayerController() const{ - if (const AGMC_Pawn* OwningPawn = GetOwnerPawn()){ - if(AGMC_PlayerController* OwningPC = Cast(OwningPawn->GetController())){ +AGMC_PlayerController* UGMCAbility::GetOwningPlayerController() const { + if (const AGMC_Pawn* OwningPawn = GetOwnerPawn()) { + if (AGMC_PlayerController* OwningPC = Cast(OwningPawn->GetController())) { return OwningPC; } } @@ -384,3 +390,15 @@ void UGMCAbility::SetOwnerJustTeleported(bool bValue) { OwnerAbilityComponent->bJustTeleported = bValue; } + +void UGMCAbility::ModifyEndOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery) +{ + EndOtherAbilitiesQuery = NewQuery; + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("CancelAbilityByDefinitionQuery modified: %s"), *NewQuery.GetDescription()); +} + +void UGMCAbility::ModifyBlockOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery) +{ + BlockOtherAbilitiesQuery = NewQuery; + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("BlockOtherAbilityByDefinitionQuery modified: %s"), *NewQuery.GetDescription()); +} diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index cd2a44f7..fc2d10e5 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -366,7 +366,9 @@ bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOfActivationBlockedTags) { @@ -1106,8 +1109,15 @@ bool UGMC_AbilitySystemComponent::CheckActivationTags(const UGMCAbility* Ability } } + // single activation query + if (!Ability->ActivationQuery.IsEmpty() && !Ability->ActivationQuery.Matches(ActiveTags)) + { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability can't activate, blocked by query: %s"), + *Ability->ActivationQuery.GetDescription()); + return false; + } + return true; - } @@ -2270,6 +2280,9 @@ UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpa { UNiagaraFunctionLibrary::SpawnSystemAtLocationWithParams(SpawnParams); }, Delay, false); + + UE_LOG(LogTemp, Warning, TEXT("Delay: %f"), Delay); + return nullptr; } diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 811dccd2..919766a8 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -65,10 +65,18 @@ void UGMCAbilityEffect::StartEffect() return; } + // Effect Query + if (!EffectData.ActivationQuery.IsEmpty() && !EffectData.ActivationQuery.Matches(OwnerAbilityComponent->GetActiveTags())) + { + EndEffect(); + return; + } + AddTagsToOwner(); AddAbilitiesToOwner(); EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnActivation); - CancelAbilitiesByDefinition(); + + EndActiveAbilitiesByDefinitionQuery(EffectData.EndAbilityOnActivationQuery); bHasAppliedEffect = true; @@ -133,6 +141,8 @@ void UGMCAbilityEffect::EndEffect() } } + EndActiveAbilitiesByDefinitionQuery(EffectData.EndAbilityOnEndQuery); + EndActiveAbilitiesFromOwner(EffectData.CancelAbilityOnEnd); RemoveTagsFromOwner(EffectData.bPreserveGrantedTagsIfMultiple); RemoveAbilitiesFromOwner(); @@ -187,6 +197,11 @@ void UGMCAbilityEffect::Tick(float DeltaTime) EndEffect(); } + // query to maintain effect + if ( !EffectData.MustMaintainQuery.IsEmpty() && EffectData.MustMaintainQuery.Matches(OwnerAbilityComponent->GetActiveTags())) + { + EndEffect(); + } // If there's a period, check to see if it's time to tick if (!IsPeriodPaused() && EffectData.Period > 0 && CurrentState == EGMASEffectState::Started) @@ -349,17 +364,25 @@ void UGMCAbilityEffect::CheckState() } } -void UGMCAbilityEffect::CancelAbilitiesByDefinition() +void UGMCAbilityEffect::EndActiveAbilitiesByDefinitionQuery(FGameplayTagQuery EndAbilityOnActivationViaDefinitionQuery) { - if (!OwnerAbilityComponent) return; - // build query here - FGameplayTagContainer TagsToMatch = EffectData.EffectDefinition; - if (TagsToMatch.Num() == 0) return; + if (EndAbilityOnActivationViaDefinitionQuery.IsEmpty()) return; - FGameplayTagQuery CancelQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(TagsToMatch); - int NumCancelled = OwnerAbilityComponent->EndAbilitiesByQuery(CancelQuery); + int NumCancelled = OwnerAbilityComponent->EndAbilitiesByQuery(EndAbilityOnActivationViaDefinitionQuery); UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Effect %s cancelled %d ability(ies) via EffectDefinition query."), *EffectData.EffectTag.ToString(), NumCancelled); } + +void UGMCAbilityEffect::ModifyMustMaintainQuery(const FGameplayTagQuery& NewQuery) +{ + EffectData.MustMaintainQuery = NewQuery; + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("MustMainQuery modified: %s"), *NewQuery.GetDescription()); +} + +void UGMCAbilityEffect::ModifyEndAbilitiesOnEndQuery(const FGameplayTagQuery& NewQuery) +{ + EffectData.EndAbilityOnEndQuery = NewQuery; + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("EndAbilityOnEndViaDefinitionQuery modified: %s"), *NewQuery.GetDescription()); +} diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index bea9f4bd..54c2bfa3 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -117,10 +117,6 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UPROPERTY(EditAnywhere, meta=(Categories="Ability"), Category = "GMCAbilitySystem") FGameplayTag AbilityTag; - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") - // Container for a more generalized definition of abilities - FGameplayTagContainer AbilityDefinition; - // An Effect that modifies attributes when the ability is activated UPROPERTY(EditAnywhere, Category = "GMCAbilitySystem") TSubclassOf AbilityCost; @@ -265,5 +261,28 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn return FString::Printf(TEXT("[name: ] %s (State %s) [Tag %s] | NumTasks %d"), *GetName(), *EnumToString(AbilityState), *AbilityTag.ToString(), RunningTasks.Num()); } + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") + // Container for a more generalized definition of abilities + FGameplayTagContainer AbilityDefinition; + + // Queries + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(DisplayName="Activation Tags Query")) + // query must match at activation + FGameplayTagQuery ActivationQuery; + + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(DisplayName="Cancel Ability via Definition Query")) + // End Abilities via Definition + FGameplayTagQuery EndOtherAbilitiesQuery; + + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(DisplayName="Block Other Ability via Definition Query")) + // Block Abilities via Definition + FGameplayTagQuery BlockOtherAbilitiesQuery; + + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + void ModifyEndOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery); + + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + void ModifyBlockOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery); + }; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 33ef5cf7..3bd9b60f 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -34,6 +34,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSyncedEvent, const FGMASSyncedEve DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActiveTagsChanged, FGameplayTagContainer, AddedTags, FGameplayTagContainer, RemovedTags); DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, const FGameplayTagContainer&, const FGameplayTagContainer&); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAbilityActivated, FGameplayTag, ActivationTag, const UInputAction*, InputAction); + USTRUCT() struct FEffectStatePrediction { @@ -412,6 +414,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo FGameplayTagContainer PreviousActiveTags; + // Called when an ability is activated + UPROPERTY(BlueprintAssignable) + FOnAbilityActivated OnAbilityActivated; + /** Returns an array of pointers to all attributes */ TArray GetAllAttributes() const; @@ -634,10 +640,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo FEffectStatePrediction EffectStatePrediction{}; TArray QueuedEffectStates; - + UPROPERTY() TMap ActiveAbilities; - UPROPERTY() TMap ActiveCooldowns; @@ -747,7 +752,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Networked FX // Is this ASC locally controlled? bool IsLocallyControlledPawnASC() const; - + // Spawn a Niagara system // IsClientPredicted: If true, the system will be spawned on the client immediately. False, the local client will spawn it when the multicast is received // bDelayByGMCSmoothing: If true, the system will be spawned with a delay for SimProxies to match the smoothing delay diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 57c96019..5efdb6f1 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -106,10 +106,6 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTag EffectTag; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - // Container for a more generalized definition of effects - FGameplayTagContainer EffectDefinition; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedTags; @@ -167,6 +163,28 @@ struct FGMCAbilityEffectData FString ToString() const{ return FString::Printf(TEXT("[id: %d] [Tag: %s] (Duration: %.3lf) (CurrentDuration: %.3lf)"), EffectID, *EffectTag.ToString(), Duration, CurrentDuration); } + + // query stuff + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + // Container for a more generalized definition of effects + FGameplayTagContainer EffectDefinition; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + // query must match on effect activation + FGameplayTagQuery ActivationQuery; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + // query must be maintained throughout effect + FGameplayTagQuery MustMaintainQuery; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem", meta = (DisplayName = "End Ability On Activation Via Definition Query")) + // end ability on effect activation if definition matches query + FGameplayTagQuery EndAbilityOnActivationQuery; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem", meta = (DisplayName = "End Ability On End Via Definition Query")) + // end ability on effect end if definition matches query + FGameplayTagQuery EndAbilityOnEndQuery; + }; /** @@ -262,7 +280,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool DuplicateEffectAlreadyApplied(); - void CancelAbilitiesByDefinition(); + void EndActiveAbilitiesByDefinitionQuery(FGameplayTagQuery); public: @@ -278,5 +296,11 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject FString ToString() { return FString::Printf(TEXT("[name: %s] (State %s) | Started: %d | Period Paused: %d | Data: %s"), *GetName(), *EnumToString(CurrentState), bHasStarted, IsPeriodPaused(), *EffectData.ToString()); } + + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + void ModifyMustMaintainQuery(const FGameplayTagQuery& NewQuery); + + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + void ModifyEndAbilitiesOnEndQuery(const FGameplayTagQuery& NewQuery); }; From d6321eed087216fb3b27ded7323fa92e4448fa65 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Thu, 8 May 2025 14:57:17 -0700 Subject: [PATCH 60/63] Fix OnAttributeChanged not being called on servers. Add PeriodTick event to Effects. --- .../Private/Ability/GMCAbility.cpp | 3 +- .../Components/GMCAbilityComponent.cpp | 67 ++++++++++++++++--- .../Private/Effects/GMCAbilityEffect.cpp | 17 +++++ .../Public/Components/GMCAbilityComponent.h | 24 +++++-- .../Public/Effects/GMCAbilityEffect.h | 6 ++ 5 files changed, 101 insertions(+), 16 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 6c41bcc7..2504bb9a 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -250,7 +250,7 @@ void UGMCAbility::OnGameplayTaskInitialized(UGameplayTask& Task) UGMCAbilityTaskBase* AbilityTask = Cast(&Task); if (!AbilityTask) { - UE_LOG(LogGMCAbilitySystem, Error, TEXT("UGMCAbility::OnGameplayTaskInitialized called with non-UGMCAbilityTaskBase task")); + // UE_LOG(LogGMCAbilitySystem, Error, TEXT("UGMCAbility::OnGameplayTaskInitialized called with non-UGMCAbilityTaskBase task")); return; } AbilityTask->SetAbilitySystemComponent(OwnerAbilityComponent); @@ -342,6 +342,7 @@ void UGMCAbility::EndAbility() if (AbilityState != EAbilityState::Ended) { FinishEndAbility(); EndAbilityEvent(); + OwnerAbilityComponent->OnAbilityEnded.Broadcast(this); } } diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 68321e93..f11a0a89 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -158,6 +158,7 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb CheckActiveTagsChanged(); CheckAttributeChanged(); + CheckUnBoundAttributeChanged(); TickActiveCooldowns(DeltaTime); @@ -584,6 +585,7 @@ void UGMC_AbilitySystemComponent::GenSimulationTick(float DeltaTime) { CheckActiveTagsChanged(); CheckAttributeChanged(); + CheckUnBoundAttributeChanged(); } if (GMCMovementComponent->GetSmoothingTargetIdx() == -1) return; @@ -1416,17 +1418,13 @@ void UGMC_AbilitySystemComponent::RPCClientQueueEffectOperation_Implementation(c void UGMC_AbilitySystemComponent::OnRep_UnBoundAttributes() { - if (OldUnBoundAttributes.Items.Num() != UnBoundAttributes.Items.Num()) { UE_LOG(LogGMCAbilitySystem, Error, TEXT("OnRep_UnBoundAttributes: Mismatched Attribute Old != New Value !")); } - - CheckUnBoundAttributeChanges(); - } -void UGMC_AbilitySystemComponent::CheckUnBoundAttributeChanges() +void UGMC_AbilitySystemComponent::CheckUnBoundAttributeChanged() { TArray& OldAttributes = OldUnBoundAttributes.Items; const TArray& CurrentAttributes = UnBoundAttributes.Items; @@ -1946,8 +1944,8 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil { if (!GMCMovementComponent->IsExecutingMove() && GetNetMode() != NM_Standalone && !bInAncillaryTick) { - - ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), + + ensureMsgf(false, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); UE_LOG(LogGMCAbilitySystem, Error, TEXT("[%20s] %s attempted a predicted removal of effects outside of a movement cycle! (%s)"), *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); @@ -2224,7 +2222,7 @@ void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifi //////////////// FX -UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, +UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystemAttached(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted, bool bDelayByGMCSmoothing) { if (SpawnParams.SystemTemplate == nullptr) @@ -2240,7 +2238,53 @@ UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpa if (HasAuthority()) { - MC_SpawnParticleSystem(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); + MC_SpawnParticleSystemAttached(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); + } + + // Sim Proxies can delay FX by the smoothing delay to better line up + if (bDelayByGMCSmoothing && !HasAuthority() && !IsLocallyControlledPawnASC()) + { + float Delay = GMCMovementComponent->GetTime() - GMCMovementComponent->GetSmoothingTime(); + FTimerHandle DelayHandle; + GetWorld()->GetTimerManager().SetTimer(DelayHandle, [this, SpawnParams]() + { + UNiagaraFunctionLibrary::SpawnSystemAttachedWithParams(SpawnParams); + }, Delay, false); + return nullptr; + } + + UNiagaraComponent* SpawnedComponent = UNiagaraFunctionLibrary::SpawnSystemAttachedWithParams(SpawnParams); + return SpawnedComponent; +} + +void UGMC_AbilitySystemComponent::MC_SpawnParticleSystemAttached_Implementation(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted, bool bDelayByGMCSmoothing) +{ + // Server already spawned + if (HasAuthority()) return; + + // Owning client already spawned + if (IsLocallyControlledPawnASC() && bIsClientPredicted) return; + + SpawnParticleSystemAttached(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); +} + +UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystemAtLocation(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted, + bool bDelayByGMCSmoothing) +{ + if (SpawnParams.SystemTemplate == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Trying to spawn FX, but FX is null!")); + return nullptr; + } + + if (SpawnParams.WorldContextObject == nullptr) + { + SpawnParams.WorldContextObject = GetWorld(); + } + + if (HasAuthority()) + { + MC_SpawnParticleSystemAtLocation(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); } // Sim Proxies can delay FX by the smoothing delay to better line up @@ -2259,7 +2303,8 @@ UNiagaraComponent* UGMC_AbilitySystemComponent::SpawnParticleSystem(FFXSystemSpa return SpawnedComponent; } -void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted, bool bDelayByGMCSmoothing) +void UGMC_AbilitySystemComponent::MC_SpawnParticleSystemAtLocation_Implementation(const FFXSystemSpawnParameters& SpawnParams, + bool bIsClientPredicted, bool bDelayByGMCSmoothing) { // Server already spawned if (HasAuthority()) return; @@ -2267,7 +2312,7 @@ void UGMC_AbilitySystemComponent::MC_SpawnParticleSystem_Implementation(const FF // Owning client already spawned if (IsLocallyControlledPawnASC() && bIsClientPredicted) return; - SpawnParticleSystem(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); + SpawnParticleSystemAtLocation(SpawnParams, bIsClientPredicted, bDelayByGMCSmoothing); } void UGMC_AbilitySystemComponent::SpawnSound(USoundBase* Sound, const FVector Location, const float VolumeMultiplier, const float PitchMultiplier, const bool bIsClientPredicted) diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index 811dccd2..651e3989 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -220,6 +220,11 @@ void UGMCAbilityEffect::PeriodTick() OwnerAbilityComponent->ApplyAbilityEffectModifier(AttributeModifier, true, false, SourceAbilityComponent); } } + PeriodTickEvent(); +} + +void UGMCAbilityEffect::PeriodTickEvent_Implementation() +{ } void UGMCAbilityEffect::UpdateState(EGMASEffectState State, bool Force) @@ -237,6 +242,18 @@ bool UGMCAbilityEffect::IsPeriodPaused() return DoesOwnerHaveTagFromContainer(EffectData.PausePeriodicEffect); } +void UGMCAbilityEffect::GetOwnerActor(AActor*& OutOwnerActor) const +{ + if (OwnerAbilityComponent) + { + OutOwnerActor = OwnerAbilityComponent->GetOwner(); + } + else + { + OutOwnerActor = nullptr; + } +} + void UGMCAbilityEffect::AddTagsToOwner() { for (const FGameplayTag Tag : EffectData.GrantedTags) diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index 5ff439c2..231f0547 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -31,6 +31,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAncillaryTick, float, DeltaTime); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSyncedEvent, const FGMASSyncedEventContainer&, EventData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAbilityEnded, UGMCAbility*, Ability); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActiveTagsChanged, FGameplayTagContainer, AddedTags, FGameplayTagContainer, RemovedTags); DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, const FGameplayTagContainer&, const FGameplayTagContainer&); @@ -271,7 +273,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION() void OnRep_UnBoundAttributes(); - void CheckUnBoundAttributeChanges(); + void CheckUnBoundAttributeChanged(); int GetNextAvailableEffectID() const; bool CheckIfEffectIDQueued(int EffectID) const; @@ -403,6 +405,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY(BlueprintAssignable) FOnActiveTagsChanged OnActiveTagsChanged; + UPROPERTY(BlueprintAssignable) + FOnAbilityEnded OnAbilityEnded; + // Called when a synced event is executed UPROPERTY(BlueprintAssignable) FOnSyncedEvent OnSyncedEvent; @@ -745,15 +750,26 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Is this ASC locally controlled? bool IsLocallyControlledPawnASC() const; - // Spawn a Niagara system + // Spawn a Niagara system attached to a component + // IsClientPredicted: If true, the system will be spawned on the client immediately. False, the local client will spawn it when the multicast is received + // bDelayByGMCSmoothing: If true, the system will be spawned with a delay for SimProxies to match the smoothing delay + UFUNCTION(BlueprintCallable, Category="GMAS|FX") + UNiagaraComponent* SpawnParticleSystemAttached(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); + + UFUNCTION(NetMulticast, Unreliable) + void MC_SpawnParticleSystemAttached(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); + + + // Spawn a Niagara system at a world location // IsClientPredicted: If true, the system will be spawned on the client immediately. False, the local client will spawn it when the multicast is received // bDelayByGMCSmoothing: If true, the system will be spawned with a delay for SimProxies to match the smoothing delay UFUNCTION(BlueprintCallable, Category="GMAS|FX") - UNiagaraComponent* SpawnParticleSystem(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); + UNiagaraComponent* SpawnParticleSystemAtLocation(FFXSystemSpawnParameters SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); UFUNCTION(NetMulticast, Unreliable) - void MC_SpawnParticleSystem(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); + void MC_SpawnParticleSystemAtLocation(const FFXSystemSpawnParameters& SpawnParams, bool bIsClientPredicted = false, bool bDelayByGMCSmoothing = false); + // Spawn a Sound at the given location UFUNCTION(BlueprintCallable, Category="GMAS|FX") void SpawnSound(USoundBase* Sound, FVector Location, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 57c96019..01a4a630 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -216,6 +216,9 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject bool AttributeDynamicCondition() const; void PeriodTick(); + + UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Period Tick"), Category="GMCAbilitySystem") + void PeriodTickEvent(); void UpdateState(EGMASEffectState State, bool Force=false); @@ -227,6 +230,9 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject // confirmed this effect within a time range, the effect will be cancelled. float ClientEffectApplicationTime; + UFUNCTION(BlueprintPure) + void GetOwnerActor(AActor*& OwnerActor) const; + protected: UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") UGMC_AbilitySystemComponent* SourceAbilityComponent = nullptr; From 3ed4b548283b8100c5876206da314186f92df520 Mon Sep 17 00:00:00 2001 From: NarsBars <150587668+NarsBars@users.noreply.github.com> Date: Sun, 11 May 2025 13:43:25 -0700 Subject: [PATCH 61/63] Effect Applied/Removed Delegates (#123) * - Added Parameterized Input Key Press Task * 5/6 * Update GMCAbilityComponent.cpp * Update GMCAbility.cpp * DefinitionTags * query fixed for effects * Update GMCAbility.cpp unnecessary warning * Remove Effects By Definition Query * Effect Delegates Effect Delegates * Remove extra params --- .../Private/Ability/GMCAbility.cpp | 2 ++ .../Private/Components/GMCAbilityComponent.cpp | 2 -- .../Private/Effects/GMCAbilityEffect.cpp | 4 ++++ .../Public/Components/GMCAbilityComponent.h | 14 +++++++++++--- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index ec2acf84..a39c8cb7 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -312,6 +312,8 @@ void UGMCAbility::BeginAbility() return; } + OwnerAbilityComponent->OnAbilityActivated.Broadcast(this, AbilityTag); + if (!BlockOtherAbilitiesQuery.IsEmpty()) { FGameplayTagQuery BlockQuery = BlockOtherAbilitiesQuery; diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index 7a4f9423..f10d8aed 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -368,8 +368,6 @@ bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOfOnEffectApplied.Broadcast(this); + // Instant effects modify base value and end instantly if (EffectData.bIsInstant) { @@ -147,6 +149,8 @@ void UGMCAbilityEffect::EndEffect() RemoveTagsFromOwner(EffectData.bPreserveGrantedTagsIfMultiple); RemoveAbilitiesFromOwner(); + OwnerAbilityComponent->OnEffectRemoved.Broadcast(this); + EndEffectEvent(); } diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index e42b27dd..d37aa8e8 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -31,12 +31,14 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAncillaryTick, float, DeltaTime); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSyncedEvent, const FGMASSyncedEventContainer&, EventData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAbilityActivated, UGMCAbility*, Ability, FGameplayTag, AbilityTag); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAbilityEnded, UGMCAbility*, Ability); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActiveTagsChanged, FGameplayTagContainer, AddedTags, FGameplayTagContainer, RemovedTags); DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, const FGameplayTagContainer&, const FGameplayTagContainer&); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAbilityActivated, FGameplayTag, ActivationTag, const UInputAction*, InputAction); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEffectApplied, UGMCAbilityEffect*, AppliedEffect); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEffectRemoved, UGMCAbilityEffect*, RemovedEffect); USTRUCT() struct FEffectStatePrediction @@ -410,6 +412,10 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UPROPERTY(BlueprintAssignable) FOnActiveTagsChanged OnActiveTagsChanged; + // Called when an ability is successfully activated + UPROPERTY(BlueprintAssignable) + FOnAbilityActivated OnAbilityActivated; + UPROPERTY(BlueprintAssignable) FOnAbilityEnded OnAbilityEnded; @@ -419,9 +425,11 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo FGameplayTagContainer PreviousActiveTags; - // Called when an ability is activated UPROPERTY(BlueprintAssignable) - FOnAbilityActivated OnAbilityActivated; + FOnEffectApplied OnEffectApplied; + + UPROPERTY(BlueprintAssignable) + FOnEffectRemoved OnEffectRemoved; /** Returns an array of pointers to all attributes */ TArray GetAllAttributes() const; From 89ebde50f13cbeaffbb2a8b757ea5a913d16383b Mon Sep 17 00:00:00 2001 From: NarsBars <150587668+NarsBars@users.noreply.github.com> Date: Sun, 11 May 2025 13:44:43 -0700 Subject: [PATCH 62/63] Rotate Yaw Towards Direction Task + Ability Task Categorization Unification (#122) * - Added Parameterized Input Key Press Task * 5/6 * Update GMCAbilityComponent.cpp * Update GMCAbility.cpp * DefinitionTags * query fixed for effects * Update GMCAbility.cpp unnecessary warning * Remove Effects By Definition Query * Rotate Yaw Towards Direction Task + Unify categorization of Ability Tasks --- .../Private/Ability/GMCAbility.cpp | 6 -- .../WaitForRotateYawTowardsDirection.cpp | 72 +++++++++++++++++++ .../Public/Ability/GMCAbility.h | 5 +- .../Public/Ability/Tasks/SetTargetDataByte.h | 2 +- .../Public/Ability/Tasks/SetTargetDataFloat.h | 2 +- .../Ability/Tasks/SetTargetDataGameplayTag.h | 2 +- .../Public/Ability/Tasks/SetTargetDataHit.h | 2 +- .../Tasks/SetTargetDataInstancedStruct.h | 2 +- .../Public/Ability/Tasks/SetTargetDataInt.h | 2 +- .../Ability/Tasks/SetTargetDataObject.h | 2 +- .../Ability/Tasks/SetTargetDataTransform.h | 2 +- .../Ability/Tasks/SetTargetDataVector3.h | 2 +- .../Public/Ability/Tasks/WaitDelay.h | 2 +- .../Ability/Tasks/WaitForGMCMontageChange.h | 2 +- .../Ability/Tasks/WaitForGameplayTagChange.h | 2 +- .../Ability/Tasks/WaitForInputKeyPress.h | 2 +- .../Tasks/WaitForInputKeyPressParameterized.h | 2 +- .../Ability/Tasks/WaitForInputKeyRelease.h | 2 +- .../Tasks/WaitForRotateYawTowardsDirection.h | 40 +++++++++++ .../Public/Effects/GMCAbilityEffect.h | 18 +++-- 20 files changed, 139 insertions(+), 32 deletions(-) create mode 100644 Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForRotateYawTowardsDirection.cpp create mode 100644 Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForRotateYawTowardsDirection.h diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index a39c8cb7..0abfa7c0 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -394,12 +394,6 @@ void UGMCAbility::SetOwnerJustTeleported(bool bValue) OwnerAbilityComponent->bJustTeleported = bValue; } -void UGMCAbility::ModifyEndOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery) -{ - EndOtherAbilitiesQuery = NewQuery; - UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("CancelAbilityByDefinitionQuery modified: %s"), *NewQuery.GetDescription()); -} - void UGMCAbility::ModifyBlockOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery) { BlockOtherAbilitiesQuery = NewQuery; diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForRotateYawTowardsDirection.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForRotateYawTowardsDirection.cpp new file mode 100644 index 00000000..c269bfc3 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForRotateYawTowardsDirection.cpp @@ -0,0 +1,72 @@ +#include "Ability/Tasks/WaitForRotateYawTowardsDirection.h" + +#include "GMCMovementUtilityComponent.h" +#include "Components/GMCAbilityComponent.h" + + +UGMCAbilityTask_RotateYawTowardsDirection::UGMCAbilityTask_RotateYawTowardsDirection(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UGMCAbilityTask_RotateYawTowardsDirection* UGMCAbilityTask_RotateYawTowardsDirection::WaitForRotateYawTowardsDirection( + UGMCAbility* OwningAbility, FVector TargetDirection, float RotationSpeed) +{ + UGMCAbilityTask_RotateYawTowardsDirection* Task = NewAbilityTask(OwningAbility); + Task->DesiredDirection = TargetDirection.GetSafeNormal(); + Task->RotationSpeed = RotationSpeed; + return Task; +} + +void UGMCAbilityTask_RotateYawTowardsDirection::Activate() +{ + Super::Activate(); + bTickingTask = true; + + MovementComponent = AbilitySystemComponent->GMCMovementComponent; + + if (!MovementComponent) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("RotateYawTowardsDirection called without a valid Movement Component")); + OnFinish(); + return; + } + + StartTime = GetWorld()->GetTimeSeconds(); + + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("RotateYawTowardsDirection activated with direction: %s, speed: %f"), + *DesiredDirection.ToString(), RotationSpeed); +} + +void UGMCAbilityTask_RotateYawTowardsDirection::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (!MovementComponent || DesiredDirection.IsNearlyZero()) + { + OnFinish(); + return; + } + + MovementComponent->RotateYawTowardsDirection(DesiredDirection, RotationSpeed, DeltaTime); + + // check if rotation is complete + const FRotator CurrentRotation = MovementComponent->GetOwner()->GetActorRotation(); + const FRotator TargetRotation = DesiredDirection.Rotation(); + const float AngleDifference = FMath::Abs(FRotator::NormalizeAxis(CurrentRotation.Yaw - TargetRotation.Yaw)); + + if (AngleDifference < 0.01f) + { + OnFinish(); + } +} + +void UGMCAbilityTask_RotateYawTowardsDirection::OnFinish() +{ + if (GetState() != EGameplayTaskState::Finished) + { + float Duration = GetWorld()->GetTimeSeconds() - StartTime; + Completed.Broadcast(Duration); + EndTask(); + } +} diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index 54c2bfa3..a0b1cb03 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -278,10 +278,7 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn // Block Abilities via Definition FGameplayTagQuery BlockOtherAbilitiesQuery; - UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") - void ModifyEndOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery); - - UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + UFUNCTION(BlueprintCallable, Category = "GMAS|Abilities|Queries") void ModifyBlockOtherAbilitiesViaDefinitionQuery(const FGameplayTagQuery& NewQuery); }; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h index 9a004b9e..5559228d 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataByte.h @@ -32,7 +32,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataByte : public UGMCAbilit virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Byte)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Byte)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataByte* SetTargetDataByte(UGMCAbility* OwningAbility, uint8 Byte); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h index d8a6f909..7de9575f 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataFloat.h @@ -32,7 +32,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataFloat : public UGMCAbili virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Float)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Float)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataFloat* SetTargetDataFloat(UGMCAbility* OwningAbility, float Float); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataGameplayTag.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataGameplayTag.h index a3bfc5d2..9050aa78 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataGameplayTag.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataGameplayTag.h @@ -39,7 +39,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataGameplayTag : public UGM virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Gameplay Tag)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Gameplay Tag)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataGameplayTag* SetTargetDataGameplayTag(UGMCAbility* OwningAbility, FGameplayTag InTag); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h index 220b8499..af56ba8f 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataHit.h @@ -40,7 +40,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataHit : public UGMCAbility virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Hit Result)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Hit Result)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataHit* SetTargetDataHit(UGMCAbility* OwningAbility, FHitResult InHit); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h index f6259f3d..3f501183 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInstancedStruct.h @@ -39,7 +39,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataInstancedStruct : public virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Instanced Struct)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Instanced Struct)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataInstancedStruct* SetTargetDataInstancedStruct(UGMCAbility* OwningAbility, FInstancedStruct InstancedStruct); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInt.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInt.h index 5616714b..37789aa0 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInt.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataInt.h @@ -32,7 +32,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataInt : public UGMCAbility virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Int)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Int)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataInt* SetTargetDataInt(UGMCAbility* OwningAbility, int Int); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataObject.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataObject.h index ab1fa52d..5bc5429c 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataObject.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataObject.h @@ -33,7 +33,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataObject : public UGMCAbil virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Object)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Object)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataObject* SetTargetDataObject(UGMCAbility* OwningAbility, UObject* Object); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h index cd6a3a9b..14854425 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataTransform.h @@ -39,7 +39,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataTransform : public UGMCA virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Transform)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Transform)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataTransform* SetTargetDataTransform(UGMCAbility* OwningAbility, FTransform Transform); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataVector3.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataVector3.h index 2f629314..89e889c9 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataVector3.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/SetTargetDataVector3.h @@ -32,7 +32,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_SetTargetDataVector3 : public UGMCAbi virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Vector3)",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Set Target Data (Vector3)", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_SetTargetDataVector3* SetTargetDataVector3(UGMCAbility* OwningAbility, FVector Vector3); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitDelay.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitDelay.h index 6d861bf8..858ef7dd 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitDelay.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitDelay.h @@ -21,7 +21,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitDelay : public UGMCAbilityTaskBas // virtual FString GetDebugString() const override; /** Wait specified time. This is functionally the same as a standard Delay node. */ - UFUNCTION(BlueprintCallable, Category="GMCAbility|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE")) + UFUNCTION(BlueprintCallable, Category = "GMAS|Abilities|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE")) static UGMCAbilityTask_WaitDelay* WaitDelay(UGMCAbility* OwningAbility, float Time); private: diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h index 60215789..4902efb7 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGMCMontageChange.h @@ -20,7 +20,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForGMCMontageChange : public UGMC // virtual FString GetDebugString() const override; /** Triggers if the montage changes (Allows for Networked Interrupt). *ONLY WORKS FOR ORGANIC MOVEMENT COMPONENT* */ - UFUNCTION(BlueprintCallable, Category="GMCAbility|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE")) + UFUNCTION(BlueprintCallable, Category = "GMAS|Abilities|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE")) static UGMCAbilityTask_WaitForGMCMontageChange* WaitForGMCMontageChange(UGMCAbility* OwningAbility); UPROPERTY() diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGameplayTagChange.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGameplayTagChange.h index 4e858de6..6c945d78 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGameplayTagChange.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForGameplayTagChange.h @@ -29,7 +29,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForGameplayTagChange : public UGM FGameplayTagContainer Tags; EGMCWaitForGameplayTagChangeType ChangeType; - UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true", HidePin="OwningAbility", DefaultToSelf="OwningAbility", DisplayName="Wait for Gameplay Tag Change"), Category="GMCAbilitySystem|Tasks") + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true", HidePin="OwningAbility", DefaultToSelf="OwningAbility", DisplayName="Wait for Gameplay Tag Change"), Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_WaitForGameplayTagChange* WaitForGameplayTagChange(UGMCAbility* OwningAbility, const FGameplayTagContainer& WatchedTags, EGMCWaitForGameplayTagChangeType ChangeType = Changed); virtual void Activate() override; diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h index 6ac66f94..3867c9fc 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPress.h @@ -25,7 +25,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyPress : public UGMCAbi virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Press",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Press", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_WaitForInputKeyPress* WaitForKeyPress(UGMCAbility* OwningAbility, bool bCheckForPressDuringActivation = true, float MaxDuration = 0.0f); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h index 2bc08cd0..a7b8a416 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyPressParameterized.h @@ -25,7 +25,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyPressParameterized : p virtual void ProgressTask(FInstancedStruct& TaskData) override; virtual void ClientProgressTask() override; - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility"), DisplayName = "Wait For Input Key Press Parameterized", Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility"), DisplayName = "Wait For Input Key Press Parameterized", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_WaitForInputKeyPressParameterized* WaitForKeyPress(UGMCAbility* OwningAbility, UInputAction* InputAction, bool bCheckForPressDuringActivation = true, float MaxDuration = 0.0f); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h index c40e9550..d2e9a945 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForInputKeyRelease.h @@ -28,7 +28,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityTask_WaitForInputKeyRelease : public UGMCA * @param bCheckForReleaseDuringActivation If true, we may complete this task during activation if the ability's input action key is already released. * @return */ - UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Release",Category = "GMCAbilitySystem/Tasks") + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"), DisplayName="Wait For Input Key Release", Category = "GMAS|Abilities|Tasks") static UGMCAbilityTask_WaitForInputKeyRelease* WaitForKeyRelease(UGMCAbility* OwningAbility, bool bCheckForReleaseDuringActivation = true, float MaxDuration = 0.0f); //Overriding BP async action base diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForRotateYawTowardsDirection.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForRotateYawTowardsDirection.h new file mode 100644 index 00000000..7acd362c --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/WaitForRotateYawTowardsDirection.h @@ -0,0 +1,40 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Ability/Tasks/GMCAbilityTaskBase.h" +#include "GMCMovementUtilityComponent.h" +#include "WaitForRotateYawTowardsDirection.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGMCAbilityTaskWaitForRotateYawTowardsDirectionOutputPin, float, Duration); + +class UGMCMovementComponent; + +UCLASS() +class GMCABILITYSYSTEM_API UGMCAbilityTask_RotateYawTowardsDirection : public UGMCAbilityTaskBase +{ + GENERATED_UCLASS_BODY() + + virtual void Activate() override; + virtual void Tick(float DeltaTime) override; + + UPROPERTY(BlueprintAssignable) + FGMCAbilityTaskWaitForRotateYawTowardsDirectionOutputPin Completed; + + UFUNCTION(BlueprintCallable, Category = "GMAS|Abilities|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE")) + static UGMCAbilityTask_RotateYawTowardsDirection* WaitForRotateYawTowardsDirection(UGMCAbility* OwningAbility, FVector TargetDirection, float RotationSpeed); + +private: + + UPROPERTY() + UGMC_MovementUtilityCmp* MovementComponent; + + UPROPERTY() + FVector DesiredDirection; + + UPROPERTY() + float RotationSpeed; + + float StartTime = 0.0f; + + void OnFinish(); +}; diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index dd034d2d..374e338b 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -214,17 +214,21 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject virtual void Tick(float DeltaTime); // Return the current duration of the effect - UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Effects") float GetCurrentDuration() const { return EffectData.CurrentDuration; } - // Return the current duration of the effect - UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + // Return the effect data struct of targeted effect + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Effects") FGMCAbilityEffectData GetEffectData() const { return EffectData; } - // Return the current duration of the effect - UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") + // Return the total duration of the effect + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Effects") float GetEffectTotalDuration() const { return EffectData.Duration; } + // Return the current remaining duration of the effect + UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Effects") + float GetEffectRemainingDuration() const { return EffectData.Duration - EffectData.CurrentDuration; } + UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Effect Tick"), Category="GMCAbilitySystem") void TickEvent(float DeltaTime); @@ -303,10 +307,10 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject return FString::Printf(TEXT("[name: %s] (State %s) | Started: %d | Period Paused: %d | Data: %s"), *GetName(), *EnumToString(CurrentState), bHasStarted, IsPeriodPaused(), *EffectData.ToString()); } - UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Effects|Queries") void ModifyMustMaintainQuery(const FGameplayTagQuery& NewQuery); - UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Query") + UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Effects|Queries") void ModifyEndAbilitiesOnEndQuery(const FGameplayTagQuery& NewQuery); }; From 656dba6fca286877a9f99f296e8bf630dc737e10 Mon Sep 17 00:00:00 2001 From: Brandon Forbes Date: Mon, 28 Jul 2025 16:32:15 -0700 Subject: [PATCH 63/63] DW Attribute Refactor (#128) * Add UPROPERTY Categories to allow compilation as engine plugin. * Adding Method CancelAbility, allowing ending an ability without triggering EndAbilityEvent. Internally, FinishEndAbility Method as also been added and ensure logics (see GAS termination of an ability) * Moving Activation tag requirement check before instantiation of the ability. * Added pre-check function for ability, allowing to have some basic test and allow to cancel an ability if they fail, Overridable in C++ or in Blueprint. * Adding Precheck and Cancelling by tag - Adding PreCheck function allowing to test logic on the start of an ability - Adding Cancelling ability by tag. * Added Activity Blocked by ActiveAbility * Fixing crash in HandleTaskHeartBeat. * Change behavior for attribute ChangeCallback - Callback OnAttributeChanged is now also called on SP - Callback is now also called when the value is affected outside for whatever reason (like by SetAttributeValueByTag) - Support Replay - SetAttributeValueByTag now also re-caculate the value. Signed-off-by: Eric Rajot * Change behavior for attribute ChangeCallback - Callback OnAttributeChanged is now also called on SP/AP - Callback is now also called when the value is affected outside for whatever reason (like by SetAttributeValueByTag) - Support Replay - SetAttributeValueByTag now also re-caculate the value. Signed-off-by: Eric Rajot * BL-279 Clean before merge - Removed Log message - Removed irrelevant bounding Signed-off-by: Eric Rajot * BL-279 Bug fixing and improvement for rep notify * BL-279 Fixing Attribute notification * BL-279 Adding Byte data type to Set Target for DW GMC Ability * Improvement for tags, added duration * BL-232 Progress * BL-232 Initial work on External Effect/Ability Pending and Tag application * BL-232 Working state. * BL-232 Refactor and cleaning, handled now by Instanced Struct * BL-232 Progress of the day * Fixing a bug in remove effect. They are now removing by specifique ID when outer removed, to ensure the list rest coherent client <-> server * BL-232 Fixing removing effect * BL-232 bug fixing in effect * Bug fixing, adding accessor * BL-232 Fix effect remove itself even is another instance is running * Added getter * Fixed name space for SetTargetDataFloat, Fixed EEffectType of GMCAbilityEffect sharing same name than playfab, Fixed wait for input key release with suggestion of Nas * Stability - Fixed name space for SetTargetDataFloat, - Fixed EEffectType of GMCAbilityEffect sharing same name than playfab - Fixed wait for input key release with suggestion of Nas - GetAbilityMapData now return a const ref. For compability, you probably want to add to core redirect those lines : ``` +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectType",NewName="/Script/GMCAbilitySystem.EGMASEffectType") +EnumRedirects=(OldName="/Script/GMCAbilitySystem.EEffectState",NewName="/Script/GMCAbilitySystem.EGMASEffectState") ``` * Adding possibility for effect to end an active ability * Changing Ability Blocking way. They are now internally stored. An Ability store itself what ability it will block, instead of other ability who will block him. This allow to modify during execution this behavior. For example, you may be want to stop an ability activation only during X time in your ability execution. * Adding a nicer way to end ability * BL-225 Grenade 100% * Fix clang error * Adding Attribute Dynamic Condition to effect. * Fix crash when an active effect has been destroyed * Module upload * GMC Update * Addition of levitation actor * Crash fix * GMC Fix for starting abilities, Fix for stamina, Fix for crash, Adding speed for orb, Adding plugin for metahuman hairs. * Update for log * Fix for GetActiveEffect ? * Typo fix * Removing unecessary log * Beginnings of general queue implementation * Further work on bound queues. * Convert abilities over to using the new bound operations queue. * Refactor to generalize ability queue processing * Convert effects to bound queue * Refactor and cleanup * A little more refactoring * Support server-auth effects queued via GMC moves. Note that this requires adding an SV_PreRemoteMoveExecution to your movement component, and calling into GMAS's PreRemoteMove call in that. * Ensure that Queue-via-GMC effects work in standalone as well. * Queue rework and cleanup finished! * Last few tweaks to the queue setup. * Further queue refactor and cleanup. New queue types (ClientAuth, PredictedQueued) added. * Add effect handles, so that PredictedQueued works right. * Small cleanup on effect handle expiry. * Correct desync for server-auth queue. * Fix crash BL-SERVER-B * Ensure we don't use the same ID if queuing two server-auth things in the same frame. * Fix for Reznok's two-effects-in-one-frame issue. * Correct issue with 2+ PredictedQueued operations in one tick, or 3+ ServerAuth queued. * BL-453 Adding application must have tag. * Fix for server compile * BL-453 - Fixing AbilityData unset from ability instanciation * Add Todo. * - Allowing Predicted effect in Ancilary tick. * [TO TEST] Moving tick of Actives Effect from Ancilarity to Predicted. Should fix the chain rollback with attribute * Fixing Crash * Re-added BL-453 Commit : fix for AbilitytData input activation tag missing from ability instanciation * Fixing crash in ability task data * Added EnsureMsfgIf to easier catch miss effect for users. TODO: Add effect tag. * BL-527 : Fixing nested tag removal in active tag. Issue reported as #98 in Reznok branch (https://github.com/reznok/GMCAbilitySystem/issues/98) -------------------------------------------- Issue was GetActiveEffectByTag() was looking also for depth in tag hierarchy. Fixed by adding an optional argument (bMatchExact), this argument has been set by default to true, and can mess with project who are calling this function if they are excepting in depht. Not ideal for retrocompability, but i prefere to enforce a good practice over the time. * Fixing issue, fix crash on revive, adding * Add CancelAbilityOnEnd to effects, allowing to stop abilities when the effect End. * Fixing : Ability blocked by tag doesn't respect hierarchy. e.g : Before -> Ability.Item as blocked tag would not block Ability.Item.Use, Ability.Item.Consume but only Ability.Item Now -> Ability.Item as blocked tag would block Ability.Item.Use, Ability.Item.Consume and Ability.Item * Adding short wrapper of out ability effect, for c++ convenience. * Adding Remove ability by tag * Unreachable attempt to catch. Try to understand why, how, etc etc. * Adding Starting effect and Ending effect event for blueprint. * Virtualised Start Effect. * Fixing bad class accessibility * Fix log spamming (thank me later blue) * Add better debug message for miss queue prediction of an effect (now display the list of concerned effect) * Display selected actor's GMAS data in debugger (#103) * Display selected actor's GMAS data in debugger * Add methods to add/remove starting effects * Server auth events 5.5 (#104) Server Auth Events! They're like server auth effects, but for events! * fix: merge conflict goofs * Remove unnecessary header includes * Keep StructUtils depdendency for older version support * Fixing missing category specifier. * Fixing version compability with 5.4 and 5.5 for instanced struct * - Fix for linux compile on GMC * Fixing crash in GMC Acknowledgments. * Fixing crash in GetAllAttributes. --- We taking copy of bound and unbound attribute struct, but for that we was using a for loop, storing the reference of the for ranged based iterator, instead of the element itself, resulting in UB once the stack was clear. The auto-inlined by the MVSC hidded the issue (i guess ?). * - Added timeout event on task to allow user to react to it - Cleanup code for engine and linux compilation * ** Input Handling Update ** - `ETriggerEvent`: Changed trigger event from `Started` to `Completed` for `AbilityInputAction` binding. - Input magnitude check: Adjusted condition to correctly detect magnitude with `!ActionValue.GetMagnitude()`. Signed-off-by: Aherys * **Added** - `KismetSystemLibrary` include in `WaitForInputKeyPress.cpp` for expanded functionality. **Updated** - Changed input trigger event from `ETriggerEvent::Completed` to `ETriggerEvent::Started` in `WaitForInputKeyPress` task for immediate keypress detection. Signed-off-by: Aherys * - `Rename` method `CancelAbilities` to `CancelConflictingAbilities` for clarity. - `Enhance` documentation for `CancelConflictingAbilities` to explain its functionality, including tag-based and query-based cancellation checks. Signed-off-by: Aherys * **GMCAbilityComponent** - `QueueAbility`: Added `bPreventConcurrentActivation` parameter to prevent concurrent activation of the same ability. - Implemented local checks in `QueueAbility` to reduce server load and handle ability concurrency locally. - Updated comments and function documentation for enhanced clarity. Signed-off-by: Aherys * **Code Cleanup** - `GMCAbilityComponent.cpp`: Removed trailing whitespace to improve code formatting. Signed-off-by: Aherys * **Refactored Attribute Processing System** - Introduced `ProcessPendingModifiers` and `AddModifier` functions for centralized modifier handling in `GMCAttributes`. - Removed redundant modifier fields (`AdditiveModifier`, `MultiplyModifier`, `DivisionModifier`) in `GMCAttributes`. - Added `ProcessAttributes` and `PurgeModifierHistory` to `GMCAbilityComponent` for improved attribute management. - Reworked `ApplyAbilityEffectModifier` to use streamlined attribute processing. - Introduced attribute-related unit tests (`FAttributeUnitTest.cpp`) for validating modifier application logic. - Updated `GMCAbilitySystem.Build.cs` to add automation dependencies for testing. ** Attribute Refactor** - Attribute over time work now based on delta time, each tick, instead of period. - Attribute will now respect network prediction - Modifier have now different priority (The phase, The priority and the Operator define the order), they can be set by the user at will. - Order of Modifier operation is now deterministic - List of Modifier for attribute : - Percent (Percent of the Actual value) - Addition (Addition of the current value) - Set To base (Reset the value to the base) - Percent On Base Value (Apply a percentage from base value to the current value) - Set to Value (Direct set to a value) - Overflow of clamp is now properly handled, including for the replay, and negate at end. - You can now set a modifier to have as value an attribute instead of a value Usefull for cross operations between attribute. - Todo : Ability to set dynamically by the code the base value of an attribute at initialisation - Todo : link back with effect system and event. * **Refactored Modifier History System** - Introduced `FModifierHistory` for cleaner separation of bound and unbound modifiers. - Implemented `AddMoveHistory`, `CleanMoveHistory`, and `ExtractFromMoveHistory` methods for better modifier handling. - Updated `ProcessPendingModifiers` to directly work with the new history structure. - Replaced `PurgeModifierHistory` and obsolete functionality in `GMCAbilityComponent`. - Enhanced test suite in `FAttributeUnitTest.cpp` to validate new modifier operations and interactions. - Improved code maintainability and fixed edge cases related to modifier history management. - Large Optimisation of the memory used by AttributeHistory (0.75mb -> 0.33mb on 50k test). - Large Optimisation of performances for move history - Split bound and unbound queue for attribute history, added the concept of agglomeration for attribute of the same action timer - Writing more than 100 units test to ensure attribute constitancy. * **Refactored Attribute Modifier System** - Updated `ProcessPendingModifiers` to return a boolean indicating attribute modification status. - Added logic to broadcast changes for unbound attributes when modified. - Improved `AttributeDynamicCondition` handling in `PeriodTick` for conditional modifier application. - Removed unused/obsolete code for cleaner implementation. * Fixing typo in comment * **Introduced Custom Modifier System** - Added `UGMCAttributeModifierCustom_Base` class for modular and extensible attribute calculations. - Implemented `UGMCModifierCustom_Exponent` with four exponent calculation types (`Mapping`, `Easing`, `Custom Power`, `Saturated`). - Updated `ProcessPendingModifiers` to support custom modifier logic via `CustomModifierClass`. - Enhanced `UGMCAbilityEffect` with `ProcessCustomModifier` function and caching for custom modifier instances. - Improved input validation and error logging for attribute modifiers. - Updated `GMCAttributes` to handle conditional and dynamic calculators efficiently. * **Refactored Effect Application and State Management** - Adjusted `HeartbeatMaxInterval` to 1.5f for improved network resilience. - Introduced `EGMCEffectAnswerState` enum to handle effect prediction and confirmation states (`Pending`, `Timeout`, `Validated`). - Enhanced effect lifecycle management with `DeclareEffect` and auto-removal logic during ability end. - Improved effect application logic, including new `ApplyAbilityEffectSafe` and `ApplyAbilityEffectShort` overloads for optional handling ability. - Added `GetActiveEffectByHandle` and `RemoveActiveAbilityEffectByHandle` methods to enhance effect access and removal. - Updated effect logging and validation for better debugging and readability. * **Enhanced Debugger and Metadata Usage, Attribute Refactor Improvements** - Added detailed client-server consistency checks and metadata enhancements to gameplay debugger categories. - Enhanced metadata (`TitleProperty`, `AdvancedDisplay`, `EditCondition`, `DisplayAfter`) for attributes, effects, and abilities for better editor usability. - Improved string formatting for `ToString` methods across abilities, attributes, and effects for cleaner debug logs. - Addressed `GMCAbilityEffect` property state coherence and added validation logic in `PostEditChangeProperty`. - Updated numerical metadata representation in attribute clamp and ability map for enhanced clarity. - Optimized memory usage and performance in modifier history and active effect systems. - Enhanced debugger display to show numerical summaries for attributes, effects, and abilities. * - new attribute system - gunflare fix - multiple helicopter extraction Signed-off-by: Aherys * Remove FORCEINLINE from AddModifier in GMCAttributes.h Signed-off-by: Aherys * Remove unused ToolMenusEditor include from GMCAttributes.cpp Signed-off-by: Aherys * - Changing the way attribute operations work with an accumulative stack, and a temporal modifier history. - BaseValue has been renamed InitialValue - Stack has been renamed RawValue - Value now refere as RawValue + Sum of TemporalModifier - Multiple operator has been added Add, AddPercentageBaseValue, AddPercentageAttribute, AddPercentageMaxClamp, AddPercentageMinClamp, AddPercentageAttributeSum, AddScaledBetween, AddClampedBetween, AddPercentageMissing. - BlockedByOtherAbility has been added to ability to allow to define from an ability, what it block outside. - GetAttributeRawValueByTag has been added. - You can now define attribute initial value at initialisation of the attribute (blueprint or c++). - Improved debugger with detection of incoherency on application, and better value/clarity - Improved QoL for creation of effect/attributes. - Better readibility for attribute map, and ability map. * - Fixed Periodic effect, they now work properly with replay and negate. * Fixing Effect type not available in blueprint. * fix: enhance error logging for unknown modifier types in FAttribute * feat: add new modifier type "AddPercentageOfAttributeRawValue" - Introduced support for the new modifier type `AddPercentageOfAttributeRawValue` in `GMCAttributeModifier`. - Updated logic to handle raw attribute values without temporal modifiers. - Improved metadata validation and configuration for added modifier type. * refactor: optimize active ability effect removal logic - Replaced iteration with `FilterByPredicate` to extract effects for removal in `GMCAbilityComponent`. - Improved code readability and performance by separating filtered effects before processing. - Simplified logic for removing predicted effects during movement or ancillary ticks. * refactor: simplify active effect removal logic in GMCAbilityComponent - Replaced `FilterByPredicate` with an explicit loop and conditional checks for clarity. - Improved readability and maintainability by iterating over `Ids` directly to collect effects for removal. * Higher task heartbeat value. --------- Signed-off-by: Eric Rajot Signed-off-by: Aherys Co-authored-by: Eric Rajot Co-authored-by: Younes Meziane Co-authored-by: Rachel Blackman Co-authored-by: Rachel Blackman Co-authored-by: Bean's Beans Studios --- .../GMCAbilitySystem.Build.cs | 7 + .../Private/Ability/GMCAbility.cpp | 86 +++++- .../Ability/Tasks/GMCAbilityTaskBase.cpp | 1 + .../Ability/Tasks/WaitForInputKeyPress.cpp | 7 +- .../Attributes/GMCAttributeModifier.cpp | 129 +++++++++ .../GMCAttributeModifierCustom_Base.cpp | 32 +++ .../Private/Attributes/GMCAttributes.cpp | 97 +++++++ .../GMCModifierCustom_Exponent.cpp | 48 ++++ .../GMCModifierCustom_Exponent.h | 41 +++ .../Components/GMCAbilityComponent.cpp | 262 ++++++++++++------ ...eplayDebuggerCategory_GMCAbilitySystem.cpp | 57 +++- .../Private/Effects/GMCAbilityEffect.cpp | 160 +++++++---- .../Public/Ability/GMCAbility.h | 37 ++- .../Public/Ability/GMCAbilityMapData.h | 2 +- .../Public/Ability/Tasks/GMCAbilityTaskBase.h | 12 +- .../Public/Attributes/FAttributeUnitTest.cpp | 7 + .../Public/Attributes/GMCAttributeModifier.h | 128 +++++++-- .../GMCAttributeModifierCustom_Base.h | 50 ++++ .../Public/Attributes/GMCAttributes.h | 152 +++++----- .../Public/Attributes/GMCAttributesData.h | 2 +- .../Public/Components/GMCAbilityComponent.h | 85 +++++- ...ameplayDebuggerCategory_GMCAbilitySystem.h | 6 + .../Public/Effects/GMCAbilityEffect.h | 82 +++--- 23 files changed, 1178 insertions(+), 312 deletions(-) create mode 100644 Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifier.cpp create mode 100644 Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifierCustom_Base.cpp create mode 100644 Source/GMCAbilitySystem/Private/Attributes/GMCAttributes.cpp create mode 100644 Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.cpp create mode 100644 Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.h create mode 100644 Source/GMCAbilitySystem/Public/Attributes/FAttributeUnitTest.cpp create mode 100644 Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifierCustom_Base.h diff --git a/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs b/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs index 520b0451..6415d6f3 100644 --- a/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs +++ b/Source/GMCAbilitySystem/GMCAbilitySystem.Build.cs @@ -35,6 +35,13 @@ public GMCAbilitySystem(ReadOnlyTargetRules Target) : base(Target) } ); + if (Target.Configuration != UnrealTargetConfiguration.Shipping) + { + PrivateDependencyModuleNames.AddRange(new string[] { + "AutomationController", + "AutomationWorker" + }); + } DynamicallyLoadedModuleNames.AddRange( new string[] diff --git a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp index 0abfa7c0..80f9e2bf 100644 --- a/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp @@ -31,6 +31,11 @@ UWorld* UGMCAbility::GetWorld() const return Contexts[0].World(); } +bool UGMCAbility::IsActive() const +{ + return AbilityState != EAbilityState::PreExecution && AbilityState != EAbilityState::Ended; +} + void UGMCAbility::Tick(float DeltaTime) { // Don't tick before the ability is initialized or after it has ended @@ -92,7 +97,7 @@ void UGMCAbility::Execute(UGMC_AbilitySystemComponent* InAbilityComponent, int I PreBeginAbility(); } -bool UGMCAbility::CanAffordAbilityCost() const +bool UGMCAbility::CanAffordAbilityCost(float DeltaTime) const { if (AbilityCost == nullptr || OwnerAbilityComponent == nullptr) return true; @@ -103,7 +108,11 @@ bool UGMCAbility::CanAffordAbilityCost() const { if (Attribute->Tag.MatchesTagExact(AttributeModifier.AttributeTag)) { - if (Attribute->Value + AttributeModifier.Value < 0) return false; + AttributeModifier.InitModifier(AbilityEffect, OwnerAbilityComponent->ActionTimer, -1.f, false, DeltaTime); + if (Attribute->Value + AttributeModifier.CalculateModifierValue(*Attribute) < 0.f) + { + return false; + } } } } @@ -130,6 +139,7 @@ void UGMCAbility::CommitAbilityCost() const UGMCAbilityEffect* EffectCDO = DuplicateObject(AbilityCost->GetDefaultObject(), this); FGMCAbilityEffectData EffectData = EffectCDO->EffectData; EffectData.OwnerAbilityComponent = OwnerAbilityComponent; + EffectData.SourceAbilityComponent = OwnerAbilityComponent; AbilityCostInstance = OwnerAbilityComponent->ApplyAbilityEffect(DuplicateObject(EffectCDO, this), EffectData); } @@ -176,7 +186,7 @@ void UGMCAbility::HandleTaskHeartbeat(int TaskID) } } -void UGMCAbility::CancelAbilities() +void UGMCAbility::CancelConflictingAbilities() { for (const auto& AbilityToCancelTag : CancelAbilitiesWithTag) { if (AbilityTag == AbilityToCancelTag) { @@ -261,12 +271,37 @@ void UGMCAbility::OnGameplayTaskDeactivated(UGameplayTask& Task) void UGMCAbility::FinishEndAbility() { + for (const TPair& Task : RunningTasks) { if (Task.Value == nullptr) continue; Task.Value->EndTaskGMAS(); } + // End handled effect + for (const auto& EfData : DeclaredEffect) + { + // Skip Auth effect removal on client + if (EfData.Value == EGMCAbilityEffectQueueType::ServerAuth && !OwnerAbilityComponent->HasAuthority()) { continue;} + + if (UGMCAbilityEffect* Effect = OwnerAbilityComponent->GetEffectById(EfData.Key)) + { + // Don't try to close effects that are already ended + if (Effect->CurrentState == EGMASEffectState::Started) + { + OwnerAbilityComponent->RemoveActiveAbilityEffectSafe(Effect, EfData.Value); + } + else + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("Effect Handle %d already ended for ability %s"), EfData.Key, *AbilityTag.ToString()); + } + } + else + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect Handle %d not found for ability %s"), EfData.Key, *AbilityTag.ToString()); + } + } + AbilityState = EAbilityState::Ended; } @@ -282,7 +317,18 @@ bool UGMCAbility::PreExecuteCheckEvent_Implementation() { } -bool UGMCAbility::PreBeginAbility() { +void UGMCAbility::DeclareEffect(int OutEffectHandle, EGMCAbilityEffectQueueType EffectType) +{ + if (DeclaredEffect.Contains(OutEffectHandle)) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect Handle %d already declared for ability %s"), OutEffectHandle, *AbilityTag.ToString()); + return; + } + DeclaredEffect.Add(OutEffectHandle, EffectType); +} + +bool UGMCAbility::PreBeginAbility() +{ if (IsOnCooldown()) { UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability Activation for %s Stopped By Cooldown"), *AbilityTag.ToString()); @@ -298,6 +344,32 @@ bool UGMCAbility::PreBeginAbility() { return false; } + + TArray ActiveAbilities; + OwnerAbilityComponent->GetActiveAbilities().GenerateValueArray(ActiveAbilities); + + for (auto& OtherAbilityTag : BlockedByOtherAbility) + { + if (ActiveAbilities.FindByPredicate([&OtherAbilityTag](const UGMCAbility* ActiveAbility) { + return ActiveAbility + && ActiveAbility->IsActive() + && ActiveAbility->AbilityTag.MatchesTag(OtherAbilityTag); + })) + { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability Activation for %s Stopped because Blocked By Other Ability (%s)"), *AbilityTag.ToString(), *OtherAbilityTag.ToString()); + CancelAbility(); + return false; + } + } + + + if (OwnerAbilityComponent->IsAbilityTagBlocked(AbilityTag)) { + UE_LOG(LogGMCAbilitySystem, Verbose, TEXT("Ability Activation for %s Stopped because Blocked By Other Ability"), *AbilityTag.ToString()); + CancelAbility(); + return false; + } + + BeginAbility(); return true; @@ -307,10 +379,6 @@ bool UGMCAbility::PreBeginAbility() { void UGMCAbility::BeginAbility() { - if (OwnerAbilityComponent->IsAbilityTagBlocked(AbilityTag)) { - CancelAbility(); - return; - } OwnerAbilityComponent->OnAbilityActivated.Broadcast(this, AbilityTag); @@ -339,7 +407,7 @@ void UGMCAbility::BeginAbility() AbilityState = EAbilityState::Initialized; // Cancel Abilities in CancelAbilitiesWithTag container - CancelAbilities(); + CancelConflictingAbilities(); // Execute BP Event BeginAbilityEvent(); diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp index 92ca9f1d..fda35bf0 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/GMCAbilityTaskBase.cpp @@ -50,6 +50,7 @@ void UGMCAbilityTaskBase::AncillaryTick(float DeltaTime){ else if (LastHeartbeatReceivedTime + HeartbeatMaxInterval < AbilitySystemComponent->ActionTimer) { UE_LOG(LogGMCReplication, Error, TEXT("Server Task Heartbeat Timeout, Cancelling Ability: %s"), *Ability->GetName()); + AbilitySystemComponent->OnTaskTimeout.Broadcast(Ability->AbilityTag); Ability->EndAbility(); EndTask(); } diff --git a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp index 87ec3be0..0884b0ca 100644 --- a/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp +++ b/Source/GMCAbilitySystem/Private/Ability/Tasks/WaitForInputKeyPress.cpp @@ -3,6 +3,7 @@ #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "Components/GMCAbilityComponent.h" +#include "Kismet/KismetSystemLibrary.h" UGMCAbilityTask_WaitForInputKeyPress* UGMCAbilityTask_WaitForInputKeyPress::WaitForKeyPress(UGMCAbility* OwningAbility, bool bCheckForPressDuringActivation, float MaxDuration) { @@ -32,6 +33,8 @@ void UGMCAbilityTask_WaitForInputKeyPress::Activate() const FEnhancedInputActionEventBinding& Binding = EnhancedInputComponent->BindAction( Ability->AbilityInputAction, ETriggerEvent::Started, this, &UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed); + + InputBindingHandle = Binding.GetHandle(); @@ -43,7 +46,8 @@ void UGMCAbilityTask_WaitForInputKeyPress::Activate() if (UEnhancedInputLocalPlayerSubsystem* InputSubSystem = ULocalPlayer::GetSubsystem(PC->GetLocalPlayer())) { ActionValue = InputSubSystem->GetPlayerInput() ? InputSubSystem->GetPlayerInput()->GetActionValue(Ability->AbilityInputAction) : FInputActionValue(); } - if (ActionValue.GetMagnitude() == 1) + + if (!ActionValue.GetMagnitude()) { InputBindingHandle = -1; ClientProgressTask(); @@ -73,6 +77,7 @@ void UGMCAbilityTask_WaitForInputKeyPress::AncillaryTick(float DeltaTime) void UGMCAbilityTask_WaitForInputKeyPress::OnKeyPressed(const FInputActionValue& InputActionValue) { + // Unbind from the input component so we don't fire multiple times. if (InputComponent) { diff --git a/Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifier.cpp b/Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifier.cpp new file mode 100644 index 00000000..086ba877 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifier.cpp @@ -0,0 +1,129 @@ +#include "Attributes/GMCAttributeModifier.h" + +#include "GMCAbilityComponent.h" + +float FGMCAttributeModifier::GetValue() const +{ + // Get The Value Type + switch (ValueType) + { + case EGMCAttributeModifierType::AMT_Value: + return ModifierValue; + case EGMCAttributeModifierType::AMT_Attribute: + { + if (SourceAbilityEffect.IsValid() && SourceAbilityEffect->GetOwnerAbilityComponent()) + { + return SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(ValueAsAttribute); + } + UE_LOG(LogGMCAbilitySystem, Error, TEXT("SourceAbilityEffect is null in FAttribute::AddModifier")); + return 0.f; + } + case EGMCAttributeModifierType::AMT_Custom: + if (CustomModifierClass && SourceAbilityEffect.IsValid() && SourceAbilityEffect->GetOwnerAbilityComponent()) + { + if (UGMCAttributeModifierCustom_Base* CustomModifier = CustomModifierClass->GetDefaultObject()) + { + return CustomModifier->Calculate(SourceAbilityEffect.Get(), SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeByTag(AttributeTag)); + } + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Custom Modifier Class is null in FAttribute::AddModifier")); + } + else + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("CustomModifierClass is null or SourceAbilityEffect/SourceAbilitySystemComponent is invalid in FAttribute::AddModifier")); + } + break; + } + + checkNoEntry() + return 0.f; +} + +float FGMCAttributeModifier::CalculateModifierValue(const FAttribute& Attribute) const +{ + float TargetValue = GetValue(); + + // First set Percentage values to a fraction + switch (Op) + { + case EModifierType::AddPercentageAttribute: + case EModifierType::AddPercentageInitialValue: + case EModifierType::AddPercentageAttributeSum: + case EModifierType::AddPercentageMissing: + case EModifierType::AddPercentageMinClamp: + case EModifierType::AddPercentageMaxClamp: + case EModifierType::AddPercentageOfAttributeRawValue: + TargetValue /= 100.f; + break; + } + + switch (Op) + { + case EModifierType::Add: + return TargetValue * DeltaTime; + case EModifierType::AddPercentageInitialValue: + return Attribute.InitialValue * TargetValue * DeltaTime; + case EModifierType::AddPercentageAttribute: + return SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(ValueAsAttribute) * TargetValue * DeltaTime; + case EModifierType::AddPercentageMaxClamp: + { + const float MaxValue = Attribute.Clamp.MaxAttributeTag.IsValid() ? SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(Attribute.Clamp.MaxAttributeTag) : Attribute.Clamp.Max; + return MaxValue * TargetValue * DeltaTime; + } + case EModifierType::AddPercentageMinClamp: + { + const float MinValue = Attribute.Clamp.MinAttributeTag.IsValid() ? SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(Attribute.Clamp.MinAttributeTag) : Attribute.Clamp.Min; + return MinValue * TargetValue * DeltaTime; + } + case EModifierType::AddPercentageAttributeSum: + { + float Sum = 0.f; + for (auto& AttTag : Attributes) + { + Sum += SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(AttTag); + } + return TargetValue * Sum * DeltaTime; + } + case EModifierType::AddScaledBetween: + { + const float XBound = XAsAttribute ? SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(XAttribute) : X; + const float YBound = YAsAttribute ? SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(YAttribute) : Y; + return FMath::Clamp(FMath::Lerp(XBound, YBound, TargetValue), X, Y) * DeltaTime; + } + case EModifierType::AddClampedBetween: + { + const float XBound = XAsAttribute ? SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(XAttribute) : X; + const float YBound = YAsAttribute ? SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeValueByTag(YAttribute) : Y; + return FMath::Clamp(TargetValue, XBound, YBound) * DeltaTime; + } + case EModifierType::AddPercentageMissing: + { + const float MissingValue = Attribute.InitialValue - Attribute.Value; + return TargetValue * MissingValue * DeltaTime; + } + case EModifierType::AddPercentageOfAttributeRawValue: + { + const float RawValue = SourceAbilityEffect->GetOwnerAbilityComponent()->GetAttributeRawValue(Attribute.Tag); + return TargetValue * RawValue * DeltaTime; + } + } + + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Unknown Modifier Type in FAttribute::AddModifier for Attribute %s, operator %d"), *Attribute.Tag.ToString(), static_cast(Op)); + checkNoEntry(); + return 0.f; +} + +void FGMCAttributeModifier::InitModifier(UGMCAbilityEffect* Effect, double InActionTimer, int InApplicationIdx, bool bInRegisterInHistory, float InDeltaTime) +{ + if (!Effect) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect or AbilitySystemComponent is null in FGMCAttributeModifier::InitModifier")); + return; + } + + SourceAbilityEffect = Effect; + bRegisterInHistory = bInRegisterInHistory; + DeltaTime = InDeltaTime; + ApplicationIndex = InApplicationIdx; + ActionTimer = InActionTimer; + +} diff --git a/Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifierCustom_Base.cpp b/Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifierCustom_Base.cpp new file mode 100644 index 00000000..ae7ec195 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Attributes/GMCAttributeModifierCustom_Base.cpp @@ -0,0 +1,32 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GMCAttributeModifierCustom_Base.h" +#include "GMCAbilitySystem/Public/Attributes/GMCAttributes.h" + +float UGMCAttributeModifierCustom_Base::Calculate(UGMCAbilityEffect* SourceEffect, const FAttribute* Attribute) +{ + if (!CheckValidity(SourceEffect, Attribute)) + { + return 0.f; // Invalid state, return 0 + } + + return K2_Calculate(SourceEffect, Attribute->Tag); +} + +bool UGMCAttributeModifierCustom_Base::CheckValidity(const UGMCAbilityEffect* SourceEffect, const FAttribute* Attribute) const +{ + if (SourceEffect == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("SourceEffect is null in UGMCAttributeModifierCustom_Base::CheckValidity")); + return false; + } + + if (Attribute == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Attribute is null in UGMCAttributeModifierCustom_Base::CheckValidity")); + return false; + } + + return true; +} diff --git a/Source/GMCAbilitySystem/Private/Attributes/GMCAttributes.cpp b/Source/GMCAbilitySystem/Private/Attributes/GMCAttributes.cpp new file mode 100644 index 00000000..f9ad0d85 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Attributes/GMCAttributes.cpp @@ -0,0 +1,97 @@ +#include "Attributes/GMCAttributes.h" + +#include "GMCAbilityComponent.h" + +void FAttribute::AddModifier(const FGMCAttributeModifier& PendingModifier) const +{ + + const float ModifierValue = PendingModifier.CalculateModifierValue(*this); + + if (PendingModifier.bRegisterInHistory) + { + ValueTemporalModifiers.Add(FAttributeTemporaryModifier(PendingModifier.ApplicationIndex, ModifierValue, PendingModifier.ActionTimer, PendingModifier.SourceAbilityEffect)); + } + else + { + RawValue = Clamp.ClampValue(RawValue + ModifierValue); + } + + bIsDirty = true; +} + +void FAttribute::CalculateValue() const +{ + + Value = Clamp.ClampValue(RawValue); + + for (auto& Mod : ValueTemporalModifiers) + { + if (Mod.InstigatorEffect.IsValid()) + { + Value += Mod.Value; + Value = Clamp.ClampValue(Value); + } + else + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Orphelin Attribute Modifier found in FAttribute::CalculateValue")); + checkNoEntry(); + } + } + + bIsDirty = false; +} + +void FAttribute::RemoveTemporalModifier(int ApplicationIndex, const UGMCAbilityEffect* InstigatorEffect) const +{ + for (int i = ValueTemporalModifiers.Num() - 1; i >= 0; i--) + { + if (ValueTemporalModifiers[i].ApplicationIndex == ApplicationIndex && ValueTemporalModifiers[i].InstigatorEffect == InstigatorEffect) + { + ValueTemporalModifiers.RemoveAt(i); + bIsDirty = true; + } + } +} + +void FAttribute::PurgeTemporalModifier(double CurrentActionTimer) +{ + + if (!bIsGMCBound) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("PurgeTemporalModifier called on an unbound attribute %s"), *Tag.ToString()); + checkNoEntry(); + return; + } + + bool bMustReprocessModifiers = false; + for (int i = ValueTemporalModifiers.Num() - 1; i >= 0; i--) + { + if (ValueTemporalModifiers[i].ActionTimer > CurrentActionTimer) + { + ValueTemporalModifiers.RemoveAt(i); + bMustReprocessModifiers = true; + } + } + + if (bMustReprocessModifiers) + { + bIsDirty = true; + } +} + +FString FAttribute::ToString() const +{ + if (bIsGMCBound) + { + return FString::Printf(TEXT("%s : %0.3f Bound[n%i/%0.2fmb]"), *Tag.ToString(), Value, ValueTemporalModifiers.Num(), ValueTemporalModifiers.GetAllocatedSize() / 1048576.0f); + } + else + { + return FString::Printf(TEXT("%s : %0.3f"), *Tag.ToString(), Value); + } +} + +bool FAttribute::operator<(const FAttribute& Other) const +{ + return Tag.ToString() < Other.Tag.ToString(); +} diff --git a/Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.cpp b/Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.cpp new file mode 100644 index 00000000..192f9f99 --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.cpp @@ -0,0 +1,48 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GMCModifierCustom_Exponent.h" + +#include "GMCAttributes.h" + +float UGMCModifierCustom_Exponent::Calculate(UGMCAbilityEffect* SourceEffect, const FAttribute* Attribute) +{ + + if (!CheckValidity(SourceEffect, Attribute)) + { + return 0.f; // Invalid state, return 0 + } + + switch (ExponentType) { + case EGMCMC_ExponentType::Mapping: + { + const float x = Attribute->Value * 3; + const float rawExp = FMath::Exp(x); + constexpr float minExp = 1.f; + constexpr float MaxExp = 0x1.42096ff2afc4p+4; // exp(3) + + return Min + ((rawExp - minExp) / (MaxExp - minExp)) * (Max - Min); + break; + } + case EGMCMC_ExponentType::Easing: + { + const float easedT = Attribute->Value == 0 ? 0 : FMath::Pow(2, 10 *(Attribute->Value - 1.f)); + return Min + easedT * (Max - Min); + break; + } + case EGMCMC_ExponentType::CustomPower: + { + const float poweredT = FMath::Pow(Attribute->Value, k); + return Min + poweredT * (Max - Min); + break; + } + case EGMCMC_ExponentType::Saturated: + { + const float expValue = 1 - FMath::Exp(-k * Attribute->Value); + return Min + expValue * (Max - Min); + break; + } + } + + return Attribute->Value; // Fallback, should not happen +} diff --git a/Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.h b/Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.h new file mode 100644 index 00000000..f446338e --- /dev/null +++ b/Source/GMCAbilitySystem/Private/Attributes/PreDefinedCustomCalculator/GMCModifierCustom_Exponent.h @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GMCAttributeModifierCustom_Base.h" +#include "GMCModifierCustom_Exponent.generated.h" + +UENUM() +enum class EGMCMC_ExponentType +{ + Mapping UMETA(DisplayName = "Mapping", ToolTip="Classique Exponent scale"), + Easing UMETA(DisplayName = "Easing", ToolTip="Easing scale"), + CustomPower UMETA(DisplayName = "Custom Power", ToolTip="Custom Power"), + Saturated UMETA(DisplayName = "Saturated", ToolTip="Saturated scale") +}; + +/** + * + */ +UCLASS() +class GMCABILITYSYSTEM_API UGMCModifierCustom_Exponent : public UGMCAttributeModifierCustom_Base +{ + GENERATED_BODY() + + public: + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Exponent) + EGMCMC_ExponentType ExponentType; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Exponent) + float Min = 0.0f; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Exponent) + float Max = 0.f; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Exponent, meta=(EditCondition = "ExponentType == EGMCMC_ExponentType::Saturated || ExponentType == EGMCMC_ExponentType::CustomPower", EditConditionHides)); + float k = 0.f; + + virtual float Calculate(UGMCAbilityEffect* SourceEffect, const FAttribute* Attribute) override; +}; diff --git a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp index f10d8aed..c05a7e47 100644 --- a/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp +++ b/Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp @@ -84,25 +84,7 @@ void UGMC_AbilitySystemComponent::BindReplicationData() // We sort our attributes alphabetically by tag so that it's deterministic. for (auto& AttributeForBind : BoundAttributes.Attributes) { - GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.Value, - EGMC_PredictionMode::ServerAuth_Output_ClientValidated, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::Periodic_Output, - EGMC_InterpolationFunction::TargetValue); - - GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.AdditiveModifier, - EGMC_PredictionMode::ServerAuth_Output_ClientValidated, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::Periodic_Output, - EGMC_InterpolationFunction::TargetValue); - - GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.MultiplyModifier, - EGMC_PredictionMode::ServerAuth_Output_ClientValidated, - EGMC_CombineMode::CombineIfUnchanged, - EGMC_SimulationMode::Periodic_Output, - EGMC_InterpolationFunction::TargetValue); - - GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.DivisionModifier, + GMCMovementComponent->BindSinglePrecisionFloat(AttributeForBind.RawValue, EGMC_PredictionMode::ServerAuth_Output_ClientValidated, EGMC_CombineMode::CombineIfUnchanged, EGMC_SimulationMode::Periodic_Output, @@ -155,11 +137,13 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb ClientHandlePendingOperation(QueuedEventOperations); - CheckActiveTagsChanged(); + + ProcessAttributes(false); + CheckAttributeChanged(); + CheckUnBoundAttributeChanged(); - TickActiveCooldowns(DeltaTime); @@ -179,6 +163,10 @@ void UGMC_AbilitySystemComponent::GenAncillaryTick(float DeltaTime, bool bIsComb bInAncillaryTick = false; } +UGMCAbilityEffect* UGMC_AbilitySystemComponent::GetActiveEffectByHandle(int EffectID) const +{ + return ActiveEffects.Contains(EffectID) ? ActiveEffects[EffectID] : nullptr; +} TArray UGMC_AbilitySystemComponent::GetActiveEffectsByTag(FGameplayTag GameplayTag, bool bMatchExact) const { @@ -371,7 +359,7 @@ bool UGMC_AbilitySystemComponent::TryActivateAbility(const TSubclassOfAbilities.IsEmpty()) return; + + // This local check will prevent concurrent activation of the same ability if the ability is contain in the input map + if (bPreventConcurrentActivation) { + for (const TPair& ActiveAbility : ActiveAbilities) + { + if (ActiveAbility.Value && MapEntry->Abilities.ContainsByPredicate([&ActiveAbility](const TSubclassOf& Ability) { + return ActiveAbility.Value->IsA(Ability); + })) return; + } + } + + + TGMASBoundQueueOperation Operation; QueuedAbilityOperations.QueueOperation(Operation, EGMASBoundQueueOperationType::Activate, InputTag, Data); } @@ -425,7 +430,7 @@ int UGMC_AbilitySystemComponent::EndAbilitiesByTag(FGameplayTag AbilityTag) { { if (ActiveAbilityData.Value->AbilityTag.MatchesTag(AbilityTag)) { - ActiveAbilityData.Value->SetPendingEnd(); + ActiveAbilityData.Value->EndAbility(); AbilitiesEnded++; } } @@ -553,11 +558,22 @@ void UGMC_AbilitySystemComponent::GenPredictionTick(float DeltaTime) ApplyStartingEffects(); + // Purge "future" temporary modifiers on replay + if (GMCMovementComponent->CL_IsReplaying()) + { + for (FAttribute& Attribute : BoundAttributes.Attributes) + { + Attribute.PurgeTemporalModifier(ActionTimer); + } + } + SendTaskDataToActiveAbility(true); TickActiveAbilities(DeltaTime); TickActiveEffects(DeltaTime); + ProcessAttributes(true); + // Abilities CleanupStaleAbilities(); @@ -649,7 +665,8 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() for(const FAttributeData AttributeData : AttributeDataAsset->AttributeData){ FAttribute NewAttribute; NewAttribute.Tag = AttributeData.AttributeTag; - NewAttribute.BaseValue = AttributeData.DefaultValue; + NewAttribute.InitialValue = AttributeData.DefaultValue; + SetAttributeInitialValue(NewAttribute.Tag, NewAttribute.InitialValue); NewAttribute.Clamp = AttributeData.Clamp; NewAttribute.Clamp.AbilityComponent = this; NewAttribute.bIsGMCBound = AttributeData.bGMCBound; @@ -670,18 +687,18 @@ void UGMC_AbilitySystemComponent::InstantiateAttributes() for (const FAttribute& Attribute : BoundAttributes.Attributes) { - Attribute.CalculateValue(); + Attribute.Init(); } for (FAttribute& Attribute : UnBoundAttributes.Items) { - Attribute.CalculateValue(); + Attribute.Init(); UnBoundAttributes.MarkItemDirty(Attribute); } for (const FAttribute& Attribute : OldUnBoundAttributes.Items) { - Attribute.CalculateValue(); + Attribute.Init(); } OldBoundAttributes = BoundAttributes; @@ -798,9 +815,12 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime) // Check for predicted effects that have not been server confirmed if (!HasAuthority() && - ProcessedEffectIDs.Contains(Effect.Key) && - !ProcessedEffectIDs[Effect.Key] && Effect.Value->ClientEffectApplicationTime + ClientEffectApplicationTimeout < ActionTimer) + !Effect.Value->EffectData.bServerAuth + && ProcessedEffectIDs.Contains(Effect.Key) + && ProcessedEffectIDs[Effect.Key] == EGMCEffectAnswerState::Pending + && Effect.Value->ClientEffectApplicationTime + ClientEffectApplicationTimeout < ActionTimer) { + ProcessedEffectIDs[Effect.Key] = EGMCEffectAnswerState::Timeout; UE_LOG(LogGMCAbilitySystem, Error, TEXT("Effect `%s` Not Confirmed By Server (ID: `%d`), Removing..."), *GetNameSafe(Effect.Value), Effect.Key); Effect.Value->EndEffect(); CompletedActiveEffects.Push(Effect.Key); @@ -831,6 +851,25 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime) } +void UGMC_AbilitySystemComponent::ProcessAttributes(bool bInGenPredictionTick) +{ + for (const FAttribute* Attribute : GetAllAttributes()) + { + if (Attribute->IsDirty() && Attribute->bIsGMCBound == bInGenPredictionTick) + { + + Attribute->CalculateValue(); + // Broadcast dirty change if unbound + if (!Attribute->bIsGMCBound) + { + UnBoundAttributes.MarkAttributeDirty(*Attribute); + } + } + } + +} + + void UGMC_AbilitySystemComponent::TickActiveAbilities(float DeltaTime) { for (const TPair& Ability : ActiveAbilities) @@ -864,16 +903,17 @@ void UGMC_AbilitySystemComponent::OnRep_ActiveEffectsData() { if (ActiveEffectData.EffectID == 0) continue; - if (!ProcessedEffectIDs.Contains(ActiveEffectData.EffectID)) + if (!ProcessedEffectIDs.Contains(ActiveEffectData.EffectID) || ProcessedEffectIDs[ActiveEffectData.EffectID] == EGMCEffectAnswerState::Timeout) { + // The client never predicted this effect, so we process it as a new effect. UGMCAbilityEffect* EffectCDO = DuplicateObject(UGMCAbilityEffect::StaticClass()->GetDefaultObject(), this); - FGMCAbilityEffectData EffectData = ActiveEffectData; - ApplyAbilityEffect(EffectCDO, EffectData); - ProcessedEffectIDs.Add(EffectData.EffectID, true); - UE_LOG(LogGMCAbilitySystem, VeryVerbose, TEXT("Replicated Effect: %d"), ActiveEffectData.EffectID); + + ApplyAbilityEffect(EffectCDO, ActiveEffectData); + ProcessedEffectIDs.Add(ActiveEffectData.EffectID, EGMCEffectAnswerState::Validated); + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("[Client] Effect [%d] %s has been force apply by the server"), ActiveEffectData.EffectID, *ActiveEffectData.EffectTag.ToString()); } - ProcessedEffectIDs[ActiveEffectData.EffectID] = true; + ProcessedEffectIDs[ActiveEffectData.EffectID] = EGMCEffectAnswerState::Validated; } } @@ -886,7 +926,7 @@ void UGMC_AbilitySystemComponent::CheckRemovedEffects() // Ensure this effect has already been confirmed by the server so that if it's now missing, // it means the server removed it - if (!ProcessedEffectIDs[Effect.Key]){return;} + if (ProcessedEffectIDs[Effect.Key] == EGMCEffectAnswerState::Pending){return;} if (!ActiveEffectsData.ContainsByPredicate([Effect](const FGMCAbilityEffectData& EffectData) {return EffectData.EffectID == Effect.Key;})) { @@ -1038,7 +1078,7 @@ void UGMC_AbilitySystemComponent::ApplyStartingEffects(bool bForce) { if (!Algo::FindByPredicate(ActiveEffects, [Effect](const TPair& ActiveEffect) { return IsValid(ActiveEffect.Value) && ActiveEffect.Value->GetClass() == Effect; })) { - ApplyAbilityEffect(Effect, FGMCAbilityEffectData{}); + ApplyAbilityEffectShort(Effect, EGMCAbilityEffectQueueType::ServerAuth); } } bStartingEffectsApplied = true; @@ -1135,6 +1175,11 @@ void UGMC_AbilitySystemComponent::ClearAbilityMap() AbilityMap.Empty(); } +void UGMC_AbilitySystemComponent::SetAttributeInitialValue(const FGameplayTag& AttributeTag, float& BaseValue) +{ + OnInitializeAttributeInitialValue(AttributeTag, BaseValue); +} + void UGMC_AbilitySystemComponent::InitializeAbilityMap(){ for (UGMCAbilityMapData* StartingAbilityMap : AbilityMaps) { @@ -1514,6 +1559,12 @@ int UGMC_AbilitySystemComponent::CreateEffectOperation( PayloadData = EffectClass->GetDefaultObject()->EffectData; } + if (QueueType == EGMCAbilityEffectQueueType::ServerAuth) + { + PayloadData.bServerAuth = true; + } + + if (bForcedEffectId) { if (PayloadData.EffectID == 0) @@ -1646,20 +1697,30 @@ void UGMC_AbilitySystemComponent::RemoveEffectHandle(int EffectHandle) void UGMC_AbilitySystemComponent::ApplyAbilityEffectSafe(TSubclassOf EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, bool& OutSuccess, int& OutEffectHandle, int& OutEffectId, - UGMCAbilityEffect*& OutEffect) + UGMCAbilityEffect*& OutEffect, UGMCAbility* HandlingAbility) { OutSuccess = ApplyAbilityEffect(EffectClass, InitializationData, QueueType, OutEffectHandle, OutEffectId, OutEffect); + + + + + if (OutSuccess && HandlingAbility) + { + HandlingAbility->DeclareEffect(OutEffectId, QueueType); + } } -UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffectShort(TSubclassOf EffectClass, EGMCAbilityEffectQueueType QueueType) { +UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffectShort(TSubclassOf EffectClass, + EGMCAbilityEffectQueueType QueueType, UGMCAbility* HandlingAbility) +{ bool bOutSuccess; int OutEffectHandle; int OutEffectId; UGMCAbilityEffect* OutEffect = nullptr; - ApplyAbilityEffectSafe(EffectClass, FGMCAbilityEffectData{}, QueueType, bOutSuccess, OutEffectHandle, OutEffectId, OutEffect); + ApplyAbilityEffectSafe(EffectClass, FGMCAbilityEffectData{}, QueueType, bOutSuccess, OutEffectHandle, OutEffectId, OutEffect, HandlingAbility); return bOutSuccess ? OutEffect : nullptr; } @@ -1677,7 +1738,8 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf Operation; const int EffectID = CreateEffectOperation(Operation, EffectClass, InitializationData, bPregenerateEffectId, QueueType); if (bPregenerateEffectId && EffectID == -1) @@ -1691,6 +1753,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOfInitializeEffect(InitializationData); @@ -1829,7 +1893,7 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(UGMCAbilityEf } else { - ProcessedEffectIDs.Add(Effect->EffectData.EffectID, false); + ProcessedEffectIDs.Add(Effect->EffectData.EffectID, EGMCEffectAnswerState::Pending); } ActiveEffects.Add(Effect->EffectData.EffectID, Effect); @@ -1849,10 +1913,31 @@ void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffect(UGMCAbilityEffect* E Effect->EndEffect(); } +void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType) +{ + UGMCAbilityEffect* Effect = GetEffectById(EffectHandle); + if (Effect == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("[%20s] %s tried to remove effect with handle %d, but it doesn't exist!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), EffectHandle); + return; + } + + RemoveActiveAbilityEffectSafe(Effect, QueueType); +} + void UGMC_AbilitySystemComponent::RemoveActiveAbilityEffectSafe(UGMCAbilityEffect* Effect, - EGMCAbilityEffectQueueType QueueType) + EGMCAbilityEffectQueueType QueueType) { - if (Effect == nullptr) return; + if (Effect == nullptr) + { + ensureAlwaysMsgf(false, TEXT("[%20s] %s tried to remove a null effect!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()); + UE_LOG(LogGMCAbilitySystem, Warning, TEXT("[%20s] %s tried to remove a null effect!"), + *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName()); + return; + } + RemoveEffectByIdSafe({ Effect->EffectData.EffectID }, QueueType); } @@ -1977,25 +2062,35 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray Ids, EGMCAbil *GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *GetEffectsNameAsString(GetEffectsByIds(Ids))); return false; } - - for (auto& Effect : ActiveEffects) { - if (Ids.Contains(Effect.Key)) { - RemoveActiveAbilityEffect(Effect.Value); + + TArray EffectsToRemove; + for (int Id : Ids) { + if (ActiveEffects.Contains(Id)) { + EffectsToRemove.Add(ActiveEffects[Id]); } } + + for (auto Effect : EffectsToRemove) { + RemoveActiveAbilityEffect(Effect); + } - return true; + return true; } case EGMCAbilityEffectQueueType::PredictedQueued: { // If in move, silenttly remove the effect as predicted if (GMCMovementComponent->IsExecutingMove() || bInAncillaryTick) { - for (auto& Effect : ActiveEffects) { - if (Ids.Contains(Effect.Key)) { - RemoveActiveAbilityEffect(Effect.Value); + TArray EffectsToRemove; + for (int Id : Ids) { + if (ActiveEffects.Contains(Id)) { + EffectsToRemove.Add(ActiveEffects[Id]); } } + + for (auto Effect : EffectsToRemove) { + RemoveActiveAbilityEffect(Effect); + } } else { @@ -2094,13 +2189,15 @@ int32 UGMC_AbilitySystemComponent::GetNumEffectByTag(FGameplayTag InEffectTag){ TArray UGMC_AbilitySystemComponent::GetAllAttributes() const{ TArray AllAttributes; - for (const FAttribute& Attribute : UnBoundAttributes.Items){ - AllAttributes.Add(&Attribute); + + for (int32 i = 0; i < UnBoundAttributes.Items.Num(); i++){ + AllAttributes.Add(&UnBoundAttributes.Items[i]); } - for (const FAttribute& Attribute : BoundAttributes.Attributes){ - AllAttributes.Add(&Attribute); + for (int32 i = 0; i < BoundAttributes.Attributes.Num(); i++){ + AllAttributes.Add(&BoundAttributes.Attributes[i]); } + return AllAttributes; } @@ -2129,7 +2226,16 @@ float UGMC_AbilitySystemComponent::GetAttributeValueByTag(const FGameplayTag Att { return Att->Value; } - return 0; + return 0.f; +} + +float UGMC_AbilitySystemComponent::GetAttributeRawValue(FGameplayTag AttributeTag) const +{ + if (const FAttribute* Att = GetAttributeByTag(AttributeTag)) + { + return Att->RawValue; + } + return 0.f; } @@ -2146,21 +2252,21 @@ bool UGMC_AbilitySystemComponent::SetAttributeValueByTag(FGameplayTag AttributeT { if (const FAttribute* Att = GetAttributeByTag(AttributeTag)) { - Att->SetBaseValue(NewValue); + /*Att->SetBaseValue(NewValue); if (bResetModifiers) { Att->ResetModifiers(); } - Att->CalculateValue(); + Att->CalculateValue();*/ UnBoundAttributes.MarkAttributeDirty(*Att); return true; } return false; } -float UGMC_AbilitySystemComponent::GetBaseAttributeValueByTag(FGameplayTag AttributeTag) const{ +float UGMC_AbilitySystemComponent::GetAttributeInitialValueByTag(FGameplayTag AttributeTag) const{ if(!AttributeTag.IsValid()){return -1.0f;} for(UGMCAttributesData* DataAsset : AttributeDataAssets){ for(FAttributeData DefaultAttribute : DataAsset->AttributeData){ @@ -2208,41 +2314,21 @@ FString UGMC_AbilitySystemComponent::GetActiveAbilitiesString() const{ #pragma endregion ToStringHelpers -void UGMC_AbilitySystemComponent::ApplyAbilityEffectModifier(FGMCAttributeModifier AttributeModifier, bool bModifyBaseValue, bool bNegateValue, UGMC_AbilitySystemComponent* SourceAbilityComponent) -{ - // Provide an opportunity to modify the attribute modifier before applying it - UGMCAttributeModifierContainer* AttributeModifierContainer = NewObject(this); - AttributeModifierContainer->AttributeModifier = AttributeModifier; +void UGMC_AbilitySystemComponent::ApplyAbilityAttributeModifier(const FGMCAttributeModifier& AttributeModifier) +{ + // Todo : re-add later // Broadcast the event to allow modifications to happen before application - OnPreAttributeChanged.Broadcast(AttributeModifierContainer, SourceAbilityComponent); - - // Apply the modified attribute modifier. If no changes were made, it's just the same as the original - // Extra copying going on here? Can this be done with a reference? BPs are weird. - AttributeModifier = AttributeModifierContainer->AttributeModifier; + //OnPreAttributeChanged.Broadcast(AttributeModifierContainer, SourceAbilityComponent); if (const FAttribute* AffectedAttribute = GetAttributeByTag(AttributeModifier.AttributeTag)) { // If attribute is unbound and this is the client that means we shouldn't predict. - if(!AffectedAttribute->bIsGMCBound && !HasAuthority()) return; - - float OldValue = AffectedAttribute->Value; - FGMCUnboundAttributeSet OldUnboundAttributes = UnBoundAttributes; - - if (bNegateValue) - { - AttributeModifier.Value = -AttributeModifier.Value; - } - AffectedAttribute->ApplyModifier(AttributeModifier, bModifyBaseValue); - - // Only broadcast a change if we've genuinely changed. - if (OldValue != AffectedAttribute->Value) - { - if (!AffectedAttribute->bIsGMCBound) - { - UnBoundAttributes.MarkAttributeDirty(*AffectedAttribute); - } + if(!AffectedAttribute->bIsGMCBound && !HasAuthority()) { + return; } + + AffectedAttribute->AddModifier(AttributeModifier); } } diff --git a/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp b/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp index 515edddb..aae0ddf2 100644 --- a/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp +++ b/Source/GMCAbilitySystem/Private/Debug/GameplayDebuggerCategory_GMCAbilitySystem.cpp @@ -23,11 +23,18 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::CollectData(APlayerController* { AbilityComponent->GMCMovementComponent->SV_SwapServerState(); DataPack.GrantedAbilities = AbilityComponent->GetGrantedAbilities().ToStringSimple(); + DataPack.NBGrantedAbilities = AbilityComponent->GetGrantedAbilities().Num(); DataPack.ActiveTags = AbilityComponent->GetActiveTags().ToStringSimple(); + DataPack.NBActiveTags = AbilityComponent->GetActiveTags().Num(); DataPack.Attributes = AbilityComponent->GetAllAttributesString(); + DataPack.NBAttributes = AbilityComponent->GetAllAttributes().Num(); DataPack.ActiveEffects = AbilityComponent->GetActiveEffectsString(); + DataPack.NBActiveEffects = AbilityComponent->GetActiveEffects().Num(); DataPack.ActiveEffectData = AbilityComponent->GetActiveEffectsDataString(); + DataPack.NBActiveEffectData = AbilityComponent->ActiveEffectsData.Num(); DataPack.ActiveAbilities = AbilityComponent->GetActiveAbilitiesString(); + DataPack.NBActiveAbilities = AbilityComponent->GetActiveAbilities().Num(); + AbilityComponent->GMCMovementComponent->SV_SwapServerState(); } } @@ -44,20 +51,38 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own { CanvasContext.Printf(TEXT("{yellow}Actor name: {white}%s"), *DataPack.ActorName); + constexpr int MaxCharDisplayAbilities = 100; // Abilities - CanvasContext.Printf(TEXT("{blue}[server] {yellow}Granted Abilities: {white}%s"), *DataPack.GrantedAbilities); + CanvasContext.Printf(TEXT("{blue}[server] {yellow}Granted Abilities (%d): {white}%s%s"), DataPack.NBGrantedAbilities, *DataPack.GrantedAbilities.Left(MaxCharDisplayAbilities), DataPack.GrantedAbilities.Len() > MaxCharDisplayAbilities ? TEXT("...") : TEXT("")); // Show client-side data if (AbilityComponent) { - CanvasContext.Printf(TEXT("{green}[client] {yellow}Granted Abilities: {white}%s"), *AbilityComponent->GetGrantedAbilities().ToStringSimple()); + if (DataPack.NBGrantedAbilities != AbilityComponent->GetGrantedAbilities().Num()) + { + CanvasContext.Printf( + TEXT("{green}[client] {yellow}Granted Abilities (%d): {red} [INCOHERENCY] {white}%s%s"), + AbilityComponent->GetGrantedAbilities().Num(), + *AbilityComponent->GetGrantedAbilities().ToStringSimple().Left(MaxCharDisplayAbilities), + AbilityComponent->GetGrantedAbilities().ToStringSimple().Len() > MaxCharDisplayAbilities ? TEXT("...") : TEXT("")); + } + else + { + CanvasContext.Printf( + TEXT("{green}[client] {yellow}Granted Abilities (%d): {white}%s%s"), AbilityComponent->GetGrantedAbilities().Num(), + *AbilityComponent->GetGrantedAbilities().ToStringSimple().Left(MaxCharDisplayAbilities), + AbilityComponent->GetGrantedAbilities().ToStringSimple().Len() > MaxCharDisplayAbilities ? TEXT("...") : TEXT("")); + } } // Active Abilities - CanvasContext.Printf(TEXT("{blue}[server] {yellow}Active Abilities: {white}%s"), *DataPack.ActiveAbilities); + CanvasContext.Printf(TEXT("\n{blue}[server] {yellow}Active Abilities: {white}%s"), *DataPack.ActiveAbilities); // Show client-side data if (AbilityComponent) { - CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Abilities: {white}%s"), *AbilityComponent->GetActiveAbilitiesString()); + if (DataPack.NBActiveAbilities != AbilityComponent->GetActiveAbilities().Num()) + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Abilities: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetActiveAbilitiesString()); + else + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Abilities: {white}%s"), *AbilityComponent->GetActiveAbilitiesString()); } // Tags @@ -65,7 +90,10 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own // Show client-side data if (AbilityComponent) { - CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Tags: {white}%s"), *AbilityComponent->GetActiveTags().ToStringSimple()); + if (DataPack.NBActiveTags != AbilityComponent->GetActiveTags().Num()) + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Tags: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetActiveTags().ToStringSimple()); + else + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Tags: {white}%s"), *AbilityComponent->GetActiveTags().ToStringSimple()); } // Attributes @@ -73,7 +101,10 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own // Show client-side data if (AbilityComponent) { - CanvasContext.Printf(TEXT("{green}[client] {yellow}Attributes: {white}%s"), *AbilityComponent->GetAllAttributesString()); + if (DataPack.NBAttributes != AbilityComponent->GetAllAttributes().Num()) + CanvasContext.Printf(TEXT("{green}[client] {yellow}Attributes: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetAllAttributesString()); + else + CanvasContext.Printf(TEXT("{green}[client] {yellow}Attributes: {white}%s"), *AbilityComponent->GetAllAttributesString()); } // Active Effects @@ -81,7 +112,10 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own // Show client-side data if (AbilityComponent) { - CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {white}%s"), *AbilityComponent->GetActiveEffectsString()); + if (DataPack.NBActiveEffects != AbilityComponent->GetActiveEffects().Num()) + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetActiveEffectsString()); + else + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {white}%s"), *AbilityComponent->GetActiveEffectsString()); } // Active Effects Data @@ -89,6 +123,9 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own // Show client-side data if (AbilityComponent) { + if (DataPack.NBActiveEffectData != AbilityComponent->ActiveEffectsData.Num()) + CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects Data: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetActiveEffectsDataString()); + else CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects Data: {white}%s"), *AbilityComponent->GetActiveEffectsDataString()); } @@ -109,6 +146,12 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::FRepData::Serialize(FArchive& A Ar << ActiveEffects; Ar << ActiveEffectData; Ar << ActiveAbilities; + Ar << NBGrantedAbilities; + Ar << NBActiveTags; + Ar << NBAttributes; + Ar << NBActiveEffects; + Ar << NBActiveEffectData; + Ar << NBActiveAbilities; } #endif \ No newline at end of file diff --git a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp index d9165783..b1ba79fd 100644 --- a/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp +++ b/Source/GMCAbilitySystem/Private/Effects/GMCAbilityEffect.cpp @@ -5,16 +5,24 @@ #include "GMCAbilitySystem.h" #include "Components/GMCAbilityComponent.h" +#include "Interfaces/IPluginManager.h" #include "Kismet/KismetSystemLibrary.h" +#if WITH_EDITOR +void UGMCAbilityEffect::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +{ + UObject::PostEditChangeProperty(PropertyChangedEvent); + + +} +#endif void UGMCAbilityEffect::InitializeEffect(FGMCAbilityEffectData InitializationData) { EffectData = InitializationData; OwnerAbilityComponent = EffectData.OwnerAbilityComponent; - SourceAbilityComponent = EffectData.SourceAbilityComponent; - + if (OwnerAbilityComponent == nullptr) { UE_LOG(LogGMCAbilitySystem, Error, TEXT("OwnerAbilityComponent is null in UGMCAbilityEffect::InitializeEffect")); @@ -42,7 +50,7 @@ void UGMCAbilityEffect::InitializeEffect(FGMCAbilityEffectData InitializationDat { EffectData.EndTime = EffectData.StartTime + EffectData.Duration; } - + // Start Immediately if (EffectData.Delay == 0) { @@ -83,38 +91,23 @@ void UGMCAbilityEffect::StartEffect() OwnerAbilityComponent->OnEffectApplied.Broadcast(this); // Instant effects modify base value and end instantly - if (EffectData.bIsInstant) + if (EffectData.EffectType == EGMASEffectType::Instant + || EffectData.EffectType == EGMASEffectType::Persistent + || (EffectData.EffectType == EGMASEffectType::Periodic && EffectData.bPeriodicFirstTick)) { - for (const FGMCAttributeModifier& Modifier : EffectData.Modifiers) + for (int i = 0; i < EffectData.Modifiers.Num(); i++) { - OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, true, false, SourceAbilityComponent); + FGMCAttributeModifier ModCpy = EffectData.Modifiers[i]; + ModCpy.InitModifier(this, OwnerAbilityComponent->ActionTimer, i, IsEffectModifiersRegisterInHistory(), 1.f); + OwnerAbilityComponent->ApplyAbilityAttributeModifier(ModCpy); } - EndEffect(); - return; - } - // Duration Effects that aren't periodic alter modifiers, not base - if (!EffectData.bIsInstant && EffectData.Period == 0) - { - EffectData.bNegateEffectAtEnd = true; - for (const FGMCAttributeModifier& Modifier : EffectData.Modifiers) + if (EffectData.EffectType == EGMASEffectType::Instant) { - OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, false, false, SourceAbilityComponent); + EndEffect(); } } - - // Tick period at start - if (EffectData.bPeriodTickAtStart && EffectData.Period > 0) - { - PeriodTick(); - } - - // Instant effects instantly end - if (EffectData.bIsInstant) - { - EndEffect(); - } - + StartEffectEvent(); UpdateState(EGMASEffectState::Started, true); @@ -123,23 +116,29 @@ void UGMCAbilityEffect::StartEffect() void UGMCAbilityEffect::EndEffect() { + // Prevent EndEffect from being called multiple times if (bCompleted) return; + bCompleted = true; if (CurrentState != EGMASEffectState::Ended) { UpdateState(EGMASEffectState::Ended, true); } + // Only remove tags and abilities if the effect has started and applied if (!bHasStarted || !bHasAppliedEffect) return; - - if (EffectData.bNegateEffectAtEnd) + // If the effect is not an instant effect, we need to negate the modifiers + if (IsEffectModifiersRegisterInHistory()) { - for (const FGMCAttributeModifier& Modifier : EffectData.Modifiers) + for (int i = 0; i < EffectData.Modifiers.Num(); i++) { - OwnerAbilityComponent->ApplyAbilityEffectModifier(Modifier, false, true); + if (const FAttribute* Attribute = OwnerAbilityComponent->GetAttributeByTag(EffectData.Modifiers[i].AttributeTag)) + { + Attribute->RemoveTemporalModifier(i, this); + } } } @@ -191,7 +190,7 @@ void UGMCAbilityEffect::Tick(float DeltaTime) return; } - EffectData.CurrentDuration += DeltaTime; + EffectData.CurrentDuration = OwnerAbilityComponent->ActionTimer - EffectData.StartTime; TickEvent(DeltaTime); // Ensure tag requirements are met before applying the effect @@ -207,20 +206,70 @@ void UGMCAbilityEffect::Tick(float DeltaTime) EndEffect(); } - // If there's a period, check to see if it's time to tick - if (!IsPeriodPaused() && EffectData.Period > 0 && CurrentState == EGMASEffectState::Started) + + if (!IsPaused() && CurrentState == EGMASEffectState::Started && AttributeDynamicCondition()) { - const float Mod = FMath::Fmod(OwnerAbilityComponent->ActionTimer, EffectData.Period); - if (Mod < PrevPeriodMod) + if (EffectData.EffectType == EGMASEffectType::Ticking) { + // If there's a period, check to see if it's time to tick + + for (int i = 0; i < EffectData.Modifiers.Num(); i++) { + FGMCAttributeModifier Modifier = EffectData.Modifiers[i]; + Modifier.InitModifier(this, OwnerAbilityComponent->ActionTimer, i, IsEffectModifiersRegisterInHistory(), DeltaTime); + OwnerAbilityComponent->ApplyAbilityAttributeModifier(Modifier); + } // End for each modifier + + + } // End Ticking + else if (EffectData.EffectType == EGMASEffectType::Periodic) { - PeriodTick(); + + + const float CurrentElapsedTime = OwnerAbilityComponent->ActionTimer - EffectData.StartTime; + float PreviousElapsedTime = CurrentElapsedTime - OwnerAbilityComponent->GMCMovementComponent->GetMoveDeltaTime(); + PreviousElapsedTime = FMath::Max(PreviousElapsedTime, 0.f); // Ensure we don't go negative + + int32 PreviousPeriod = FMath::TruncToInt(PreviousElapsedTime / EffectData.PeriodicInterval); + int32 CurrentPeriod = FMath::TruncToInt(CurrentElapsedTime / EffectData.PeriodicInterval); + + if (CurrentPeriod > PreviousPeriod) { + int32 NumTickToApply = CurrentPeriod - PreviousPeriod; + + for (int i = 0; i < NumTickToApply; i++) { + for (int y = 0; y < EffectData.Modifiers.Num(); y++) { + FGMCAttributeModifier Modifier = EffectData.Modifiers[y]; + Modifier.InitModifier(this, OwnerAbilityComponent->ActionTimer, y, IsEffectModifiersRegisterInHistory(), 1.f); + OwnerAbilityComponent->ApplyAbilityAttributeModifier(Modifier); + } + } + + if (NumTickToApply > 0) + { + PeriodTick(); + } + } + } - PrevPeriodMod = Mod; } + + + CheckState(); } +int32 UGMCAbilityEffect::CalculatePeriodicTicksBetween(float Period, float StartActionTimer, float EndActionTimer) +{ + if (Period <= 0.0f || EndActionTimer <= StartActionTimer) { return 0; } + + float FirstTick = FMath::CeilToFloat(StartActionTimer / Period) * Period; + if (FirstTick > EndActionTimer) { return 0; } + + + float LastTick = FMath::FloorToFloat(EndActionTimer / Period) * Period; + + return FMath::RoundToInt((LastTick - FirstTick) / Period) + 1; +} + void UGMCAbilityEffect::TickEvent_Implementation(float DeltaTime) { } @@ -233,12 +282,6 @@ bool UGMCAbilityEffect::AttributeDynamicCondition_Implementation() const { void UGMCAbilityEffect::PeriodTick() { - if (AttributeDynamicCondition()) { - for (const FGMCAttributeModifier& AttributeModifier : EffectData.Modifiers) - { - OwnerAbilityComponent->ApplyAbilityEffectModifier(AttributeModifier, true, false, SourceAbilityComponent); - } - } PeriodTickEvent(); } @@ -256,9 +299,31 @@ void UGMCAbilityEffect::UpdateState(EGMASEffectState State, bool Force) CurrentState = State; } -bool UGMCAbilityEffect::IsPeriodPaused() +bool UGMCAbilityEffect::IsPaused() +{ + return DoesOwnerHaveTagFromContainer(EffectData.PauseEffect); +} + +bool UGMCAbilityEffect::IsEffectModifiersRegisterInHistory() const +{ + return EffectData.EffectType != EGMASEffectType::Instant && EffectData.bNegateEffectAtEnd; +} + +float UGMCAbilityEffect::ProcessCustomModifier(const TSubclassOf& MCClass, const FAttribute* Attribute) { - return DoesOwnerHaveTagFromContainer(EffectData.PausePeriodicEffect); + UGMCAttributeModifierCustom_Base** MCI = CustomModifiersInstances.Find(MCClass); + if (MCI == nullptr) + { + MCI = &CustomModifiersInstances.Add(MCClass, NewObject(this, MCClass)); + } + + if (*MCI == nullptr) + { + UE_LOG(LogGMCAbilitySystem, Error, TEXT("Custom Modifier Instance is null for class %s in UGMCAbilityEffect::ProcessCustomModifier"), *MCClass->GetName()); + return 0.f; + } + + return (*MCI)->Calculate(this, Attribute); } void UGMCAbilityEffect::GetOwnerActor(AActor*& OutOwnerActor) const @@ -331,7 +396,6 @@ void UGMCAbilityEffect::EndActiveAbilitiesFromOwner(const FGameplayTagContainer& } } - bool UGMCAbilityEffect::DoesOwnerHaveTagFromContainer(FGameplayTagContainer& TagContainer) const { for (const FGameplayTag Tag : TagContainer) diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h index a0b1cb03..9809d8f8 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbility.h @@ -33,7 +33,8 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem") virtual UWorld* GetWorld() const override; - + + //// Ability State // EAbilityState. Use Getters/Setters UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") @@ -47,7 +48,8 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UFUNCTION() int GetNextTaskID(){TaskIDCounter += 1; return TaskIDCounter;} - + + bool IsActive() const; int GetAbilityID() const {return AbilityID;};; @@ -75,6 +77,9 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn UFUNCTION(BlueprintNativeEvent, meta=(DisplayName="Ability PreExecution Check"), Category="GMCAbilitySystem|Ability") bool PreExecuteCheckEvent(); + // Declare an effect, that will allow to automatically end it when the ability ends + void DeclareEffect(int OutEffectHandle, EGMCAbilityEffectQueueType EffectType); + UFUNCTION() virtual bool PreBeginAbility(); @@ -138,8 +143,9 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn bool bAllowMultipleInstances {false}; // Check to see if affected attributes in the AbilityCost would still be >= 0 after committing the cost + // Delta time can be required if the cost is time based. UFUNCTION(BlueprintPure, Category = "GMCAbilitySystem") - virtual bool CanAffordAbilityCost() const; + virtual bool CanAffordAbilityCost(float DeltaTime = 1.f) const; // Apply the effects in AbilityCost and (Re-)apply the CooldownTime of this ability // Warning : Will apply CooldownTime regardless of already being on cooldown @@ -206,7 +212,25 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn // Prevent Abilities with these tags from activating when this ability is activated FGameplayTagContainer BlockOtherAbility; - virtual void CancelAbilities(); + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(Categories="Ability")) + // If those ability are active, they will prevent this ability from activating + FGameplayTagContainer BlockedByOtherAbility; + + /** + * Cancels active abilities based on specific conditions. + * + * This method performs the cancellation of abilities on the owner ability component depending on the tags specified in + * `CancelAbilitiesWithTag` and `EndOtherAbilitiesQuery`. It also prevents an ability from unintentionally canceling itself. + * + * Abilities are checked and canceled as follows: + * - Each tag in `CancelAbilitiesWithTag` is evaluated against the current ability tag (`AbilityTag`). If the tag matches, + * the current ability is skipped. + * - Calls `EndAbilitiesByTag` on the owner ability component for abilities matching a tag in `CancelAbilitiesWithTag`. + * - Iterates through the active abilities in the owner ability component and checks if they match the query in + * `EndOtherAbilitiesQuery`. If a match is found, those abilities are set to pending end. + * + */ + virtual void CancelConflictingAbilities(); /** * If true, activate on movement tick, if false, activate on ancillary tick. Defaults to true. @@ -258,13 +282,16 @@ class GMCABILITYSYSTEM_API UGMCAbility : public UObject, public IGameplayTaskOwn public: FString ToString() const{ - return FString::Printf(TEXT("[name: ] %s (State %s) [Tag %s] | NumTasks %d"), *GetName(), *EnumToString(AbilityState), *AbilityTag.ToString(), RunningTasks.Num()); + return FString::Printf(TEXT("[name: %s] [Tag %s] (%s) | NumTasks %d"), *GetName().Left(30), *AbilityTag.ToString(), *EnumToString(AbilityState), RunningTasks.Num()); } UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") // Container for a more generalized definition of abilities FGameplayTagContainer AbilityDefinition; + + TMap DeclaredEffect; + // Queries UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(DisplayName="Activation Tags Query")) // query must match at activation diff --git a/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h b/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h index 6dd93d84..361de722 100644 --- a/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h +++ b/Source/GMCAbilitySystem/Public/Ability/GMCAbilityMapData.h @@ -33,7 +33,7 @@ UCLASS() class GMCABILITYSYSTEM_API UGMCAbilityMapData : public UPrimaryDataAsset{ GENERATED_BODY() - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(TitleProperty="{InputTag}")) TArray AbilityMapData; public: diff --git a/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h b/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h index f2795a31..4ba885dc 100644 --- a/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h +++ b/Source/GMCAbilitySystem/Public/Ability/Tasks/GMCAbilityTaskBase.h @@ -7,6 +7,8 @@ class UGMC_AbilitySystemComponent; class UGameplayTasksComponent; + + UCLASS(Abstract, BlueprintType, meta = (ExposedAsyncProxy=AsyncTask), config = Game) class GMCABILITYSYSTEM_API UGMCAbilityTaskBase : public UGameplayTask { @@ -66,7 +68,6 @@ class GMCABILITYSYSTEM_API UGMCAbilityTaskBase : public UGameplayTask virtual void Heartbeat(); - protected: bool bTaskCompleted; @@ -78,13 +79,14 @@ class GMCABILITYSYSTEM_API UGMCAbilityTaskBase : public UGameplayTask private: // How often client sends heartbeats to server - float HeartbeatInterval = .1f; + float HeartbeatInterval = .2f; // Max time between heartbeats before server cancels task - float HeartbeatMaxInterval =.3f; + // Aherys: previous value was 0.3f it's maybe a bit too low for harsh network conditions + float HeartbeatMaxInterval = 3.f; - float ClientLastHeartbeatSentTime; - float LastHeartbeatReceivedTime; + float ClientLastHeartbeatSentTime = 0.f; + float LastHeartbeatReceivedTime = 0.f; }; diff --git a/Source/GMCAbilitySystem/Public/Attributes/FAttributeUnitTest.cpp b/Source/GMCAbilitySystem/Public/Attributes/FAttributeUnitTest.cpp new file mode 100644 index 00000000..9b91c419 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Attributes/FAttributeUnitTest.cpp @@ -0,0 +1,7 @@ +// ReSharper disable CppExpressionWithoutSideEffects +#include "GMCAttributeModifier.h" +#include "GMCAttributes.h" +#include "Misc/AutomationTest.h" + +//IMPLEMENT_SIMPLE_AUTOMATION_TEST(FAttributeNewOperatorsTest, "UE5.Marketplace.DeepWorlds_GMCAbilitySystem.Source.GMCAbilitySystem.Public.Attributes.NewOperatorsTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) + diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifier.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifier.h index d3fef80f..bd0d3055 100644 --- a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifier.h +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifier.h @@ -1,36 +1,130 @@ #pragma once #include "GameplayTags.h" +#include "GMCAttributeModifierCustom_Base.h" #include "GMCAttributeModifier.generated.h" + +class UGMCAbilityEffect; +class UGMC_AbilitySystemComponent; + UENUM(BlueprintType) enum class EModifierType : uint8 { - // Adds to value - Add, - // Adds to value multiplier. Base Multiplier is 1. A modifier value of 1 will double the value. - Multiply, - // Adds to value divisor. Base Divisor is 1. A modifier value of 1 will halve the value. - Divide + Add UMETA(DisplayName = "+ [Add]"), + // Add To Attribute a Percentage of the Base Value + AddPercentageInitialValue UMETA(DisplayName = "% [Add Percentage Of Initial Value]"), + // Add to Attribute the Percentage of an selected Attribute (Value is the Percentage, Attribute as Value is the Attribute to use) + AddPercentageAttribute UMETA(DisplayName = "% [Add Percentage Of Attribute]"), + // Add to Attribute the Percentage of the Max Clamp Value + AddPercentageMaxClamp UMETA(DisplayName = "% [Add Percentage Of Max Clamp]"), + // Add to Attribute the Percentage of the Min Clamp Value + AddPercentageMinClamp UMETA(DisplayName = "% [Add Percentage Of Min Clamp]"), + // Add to Attribute the Percentage of the sum of selecteds Attributes (+20% of the Intelligence + Strength) + AddPercentageAttributeSum UMETA(DisplayName = "% [Add Percentage Of Attribute Sum]"), + // Add to Attribute a Value Scaled between X and Y depending of Value + AddScaledBetween UMETA(DisplayName = "X-y [Scaled Between]"), + // Add to Attribute a value, but this is Clamped between X and Y + AddClampedBetween UMETA(DisplayName = "+ [Add Clamped]"), + // Add to Attribute the Percentage of the Missing Value compare to the Base Value + AddPercentageMissing UMETA(DisplayName = "% [Add Percentage Of Missing Value]"), + // Add to Attribute the Percentage of an Attribute Raw Value (Raw Value is the attribute value without any temporal modifiers) + AddPercentageOfAttributeRawValue UMETA(DisplayName = "% [Add Percentage Of Attribute Raw Value"), +}; + +UENUM(BlueprintType) +enum class EGMCAttributeModifierType : uint8 +{ + AMT_Value UMETA(DisplayName = "Value", ToolTip = "Raw Value"), + AMT_Attribute UMETA(DisplayName = "Attribute", ToolTip = "Attribute that will be used to calculate the value"), + AMT_Custom UMETA(DisplayName = "Custom", ToolTip = "Custom modifier class that will be used to calculate the value"), }; USTRUCT(BlueprintType) struct FGMCAttributeModifier { GENERATED_BODY() + + public: + + // Return the value (Auto apply Custom Modifier if set, or return target attribute, or raw value) + float GetValue() const; + + // Return the value to apply to an attribute on calculation. + float CalculateModifierValue(const FAttribute& Attribute) const; + + // If isn't ticking, set DeltaTime to 1.f ! + void InitModifier(UGMCAbilityEffect* Effect, double InActionTimer, int InApplicationIdx, bool bInRegisterInHistory = false, float + InDeltaTime = 1.f); - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Attribute", meta = (Categories="Attribute")) - FGameplayTag AttributeTag; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Attribute", meta = (Categories="Attribute")) + FGameplayTag AttributeTag; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Attribute", meta=(DisplayAfter = "AttributeTag")) + EGMCAttributeModifierType ValueType {EGMCAttributeModifierType::AMT_Value}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Attribute", meta = (Categories="Attribute", EditConditionHides, + EditCondition = "ValueType == EGMCAttributeModifierType::AMT_Attribute || Op == EModifierType::AddPercentageAttribute || Op == EModifierType::AddPercentageOfAttributeRawValue", + DisplayAfter = "ValueType")) + FGameplayTag ValueAsAttribute; + + UPROPERTY(Transient) + TWeakObjectPtr SourceAbilityEffect{nullptr}; + + UPROPERTY(Transient) + bool bRegisterInHistory{false}; + + float DeltaTime {1.f}; + + double ActionTimer {0.0}; + + int ApplicationIndex{0}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") + EModifierType Op{EModifierType::Add}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(DisplayAfter = "ValueType", EditConditionHides, EditCondition = "ValueType == EGMCAttributeModifierType::AMT_Custom")) + TSubclassOf CustomModifierClass{nullptr}; + + // Metadata tags to be passed with the attribute + // Ie: DamageType (Element.Fire, Element.Electric), DamageSource (Source.Player, Source.Boss), etc + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") + FGameplayTagContainer MetaTags; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(EditCondition = "ValueType == EGMCAttributeModifierType::AMT_Value", EditConditionHides, DisplayAfter = "ValueType")) + float ModifierValue{0}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(EditCondition = "(Op == EModifierType::AddScaledBetween || Op == EModifierType::AddClampedBetween) && !XAsAttribute", EditConditionHides + , DisplayAfter = "XAsAttribute")) + float X {0.f}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(EditCondition = "(Op == EModifierType::AddScaledBetween || Op == EModifierType::AddClampedBetween) && !YAsAttribute", EditConditionHides + , DisplayAfter = "YAsAttribute")) + float Y {0.f}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", DisplayName="X As Attribute", + meta=(EditCondition = "Op == EModifierType::AddScaledBetween || Op == EModifierType::AddClampedBetween", EditConditionHides)); + bool XAsAttribute{false}; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", DisplayName="Y As Attribute", + meta=(EditCondition = "Op == EModifierType::AddScaledBetween || Op == EModifierType::AddClampedBetween", EditConditionHides)); + bool YAsAttribute{false}; - // Value to modify the attribute by - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") - float Value{0}; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(EditCondition = "(Op == EModifierType::AddScaledBetween || Op == EModifierType::AddClampedBetween) && XAsAttribute", EditConditionHides, + DisplayAfter = "XAsAttribute")) + FGameplayTag XAttribute; - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") - EModifierType ModifierType{EModifierType::Add}; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(EditCondition = "(Op == EModifierType::AddScaledBetween || Op == EModifierType::AddClampedBetween) && YAsAttribute", EditConditionHides, + DisplayAfter = "YAsAttribute")) + FGameplayTag YAttribute; - // Metadata tags to be passed with the attribute - // Ie: DamageType (Element.Fire, Element.Electric), DamageSource (Source.Player, Source.Boss), etc - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") - FGameplayTagContainer MetaTags; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", + meta=(EditCondition = "Op == EModifierType::AddPercentageAttributeSum", EditConditionHides, DisplayAfter = "ValueType")) + FGameplayTagContainer Attributes; }; \ No newline at end of file diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifierCustom_Base.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifierCustom_Base.h new file mode 100644 index 00000000..3e368005 --- /dev/null +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributeModifierCustom_Base.h @@ -0,0 +1,50 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GMCAbilitySystem.h" +#include "UObject/Object.h" +#include "GMCAttributeModifierCustom_Base.generated.h" + +struct FAttribute; +class UGMCAbilityEffect; +class UGMC_AbilitySystemComponent; +/** + * @class UGMCAttributeModifierCustom_Base + * + * @brief Defines a custom calculator for applying modifiers to attributes in a system. + * + * The UGMCAttributeModifierCustomCalculator class provides the capability to calculate + * and apply custom attribute modifications based on user-defined logic. This is particularly + * useful in situations where attributes need to be adjusted dynamically based on specific + * conditions or custom input. + * + * It supports plug-and-play functionality for entities that utilize modifiable attributes, + * allowing users to define and override custom calculation logic if required. + * + * This class is expected to serve as a base class or utility for systems where attribute + * modifiers do not follow a predefined rule set and need bespoke calculations. + * + * Features include: + * - Extension support for custom logic and behaviors. + * - Compatibility with general modifier systems. + */ +UCLASS(Blueprintable) +class GMCABILITYSYSTEM_API UGMCAttributeModifierCustom_Base : public UObject +{ + GENERATED_BODY() + + public: + + UFUNCTION(BlueprintImplementableEvent) + float K2_Calculate(UGMCAbilityEffect* SourceEffect, FGameplayTag AttributeTag) const; + + // Designed to be overridden in C++ or Blueprint, this function will be called to calculate the final value of the attribute modifier. + // If override in C++, don't call super to avoid calling the Blueprint event and pay the performance cost of the Blueprint call. + virtual float Calculate(UGMCAbilityEffect* SourceEffect, const FAttribute* Attribute); + +protected: + bool CheckValidity(const UGMCAbilityEffect* SourceEffect, const FAttribute* Attribute) const; +}; diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h index a5a62cad..0a275e56 100644 --- a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributes.h @@ -7,103 +7,80 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAttributeChanged, float, OldValue, float, NewValue); -USTRUCT(BlueprintType) -struct GMCABILITYSYSTEM_API FAttribute : public FFastArraySerializerItem + + +USTRUCT() +struct FModifierHistoryEntry { GENERATED_BODY() - FAttribute(){}; - void Init() const - { - CalculateValue(false); - } + TWeakObjectPtr InstigatorEffect; + + float ActionTimer = 0.f; - UPROPERTY(BlueprintAssignable) - FAttributeChanged OnAttributeChanged; + float Value = 0.f; +}; + +USTRUCT(Blueprintable) +struct FAttributeTemporaryModifier +{ + GENERATED_BODY() UPROPERTY() - mutable float AdditiveModifier{0}; - + // Index used to identify the application of this modifier + int ApplicationIndex = 0; + + UPROPERTY() + // The value that we would like to apply + float Value = 0.f; + UPROPERTY() - mutable float MultiplyModifier{1}; + double ActionTimer = 0.0; + // The effect that applied this modifier UPROPERTY() - mutable float DivisionModifier{1}; + TWeakObjectPtr InstigatorEffect = nullptr; +}; + +USTRUCT(BlueprintType) +struct GMCABILITYSYSTEM_API FAttribute : public FFastArraySerializerItem +{ + GENERATED_BODY() + FAttribute(){}; - void ApplyModifier(const FGMCAttributeModifier& Modifier, bool bModifyBaseValue) const + void Init() const { - switch(Modifier.ModifierType) - { - case EModifierType::Add: - if (bModifyBaseValue) - { - BaseValue += Modifier.Value; - BaseValue = Clamp.ClampValue(BaseValue); - } - else - { - AdditiveModifier += Modifier.Value; - } - break; - case EModifierType::Multiply: - MultiplyModifier += Modifier.Value; - break; - case EModifierType::Divide: - DivisionModifier += Modifier.Value; - break; - default: - break; - } - + RawValue = Clamp.ClampValue(InitialValue); CalculateValue(); - } - void CalculateValue(bool bClamp = true) const - { - // Prevent divide by 0 and negative divisors - float LocalDivisionModifier = DivisionModifier; - if (LocalDivisionModifier <= 0){ - LocalDivisionModifier = 1; - } + + void AddModifier(const FGMCAttributeModifier& PendingModifier) const; - // Prevent negative multipliers - float LocalMultiplyModifier = MultiplyModifier; - if (LocalMultiplyModifier < 0){ - LocalMultiplyModifier = 0; - } - - Value =((BaseValue + AdditiveModifier) * LocalMultiplyModifier) / LocalDivisionModifier; - if (bClamp) - { - Value = Clamp.ClampValue(Value); - } - } + // Return true if the attribute has been modified + void CalculateValue() const; - // Reset the modifiers to the base value. May cause jank if there's effects going on. - void ResetModifiers() const - { - MultiplyModifier = 1; - DivisionModifier = 1; - } + void RemoveTemporalModifier(int ApplicationIndex, const UGMCAbilityEffect* InstigatorEffect) const; - // Allow for externally directly setting the BaseValue - // Usually preferred to go through Effects/Modifiers instead of this - void SetBaseValue(const float NewValue) const - { - BaseValue = Clamp.ClampValue(NewValue); - } + // Used to purge "future modifiers" during replay + void PurgeTemporalModifier(double CurrentActionTimer); + + UPROPERTY(BlueprintAssignable) + FAttributeChanged OnAttributeChanged; + + // Temporal Modifier + Accumulated Value UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") mutable float Value{0}; + // Value when the attribute has been initialized UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - mutable float BaseValue{0}; + mutable float InitialValue{0}; // Attribute.* UPROPERTY(EditDefaultsOnly, Category="Attribute", meta = (Categories="Attribute")) FGameplayTag Tag{FGameplayTag::EmptyTag}; - + // Whether this should be bound over GMC or not. // NOTE: If you don't bind it, you can't use it for any kind of prediction. UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") @@ -111,19 +88,33 @@ struct GMCABILITYSYSTEM_API FAttribute : public FFastArraySerializerItem // Clamp the attribute to a certain range // Clamping will only happen if this is modified - UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem") + UPROPERTY(EditDefaultsOnly, Category = "GMCAbilitySystem", meta=(TitleProperty="({min}, {max} {MinAttributeTag}, {MaxAttributeTag})")) FAttributeClamp Clamp{}; - FString ToString() const{ - return FString::Printf(TEXT("%s : %f (Bound: %d)"), *Tag.ToString(), Value, bIsGMCBound); - } + FString ToString() const; - bool operator < (const FAttribute& Other) const + bool IsDirty() const { - return Tag.ToString() < Other.Tag.ToString(); + return bIsDirty; } + + bool operator< (const FAttribute& Other) const; + + // This is the sum of permanent modification applied to this attribute. + UPROPERTY() + mutable float RawValue = 0.f; + +protected: + + UPROPERTY() + mutable TArray ValueTemporalModifiers; + + mutable bool bIsDirty = false; + }; + + USTRUCT(BlueprintType) struct GMCABILITYSYSTEM_API FGMCAttributeSet{ GENERATED_BODY() @@ -175,10 +166,11 @@ struct FGMCUnboundAttributeSet : public FFastArraySerializer } } + + bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams) { - return FFastArraySerializer::FastArrayDeltaSerialize(Items, DeltaParams, - *this); + return FFastArraySerializer::FastArrayDeltaSerialize(Items, DeltaParams,*this); } }; @@ -187,6 +179,6 @@ struct TStructOpsTypeTraits : public TStructOpsTypeTrai { enum { - WithNetDeltaSerializer = true + WithNetDeltaSerializer = true, }; }; \ No newline at end of file diff --git a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributesData.h b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributesData.h index 3c29ba52..27c18b2e 100644 --- a/Source/GMCAbilitySystem/Public/Attributes/GMCAttributesData.h +++ b/Source/GMCAbilitySystem/Public/Attributes/GMCAttributesData.h @@ -37,6 +37,6 @@ class GMCABILITYSYSTEM_API UGMCAttributesData : public UPrimaryDataAsset{ GENERATED_BODY() public: - UPROPERTY(EditDefaultsOnly, Category="AttributeData") + UPROPERTY(EditDefaultsOnly, Category="AttributeData", meta=(TitleProperty="{AttributeTag} ({DefaultValue})")) TArray AttributeData; }; diff --git a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h index d37aa8e8..d4b7c600 100644 --- a/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h +++ b/Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h @@ -1,4 +1,3 @@ -// Fill out your copyright notice in the Description page of Project Settings. #pragma once @@ -11,6 +10,7 @@ #include "Ability/Tasks/GMCAbilityTaskData.h" #include "Effects/GMCAbilityEffect.h" #include "Components/ActorComponent.h" +#include "Containers/Deque.h" #include "Utility/GMASBoundQueue.h" #include "Utility/GMASSyncedEvent.h" #include "GMCAbilityComponent.generated.h" @@ -40,6 +40,8 @@ DECLARE_MULTICAST_DELEGATE_TwoParams(FGameplayTagFilteredMulticastDelegate, cons DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEffectApplied, UGMCAbilityEffect*, AppliedEffect); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEffectRemoved, UGMCAbilityEffect*, RemovedEffect); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTaskTimeout, FGameplayTag, TaskTag); + USTRUCT() struct FEffectStatePrediction { @@ -93,6 +95,17 @@ enum class EGMCAbilityEffectQueueType : uint8 ServerAuthMove UMETA(Hidden, DisplayName="ADVANCED: Server Auth [Movement Cycle]") }; +UENUM(BlueprintType) +enum class EGMCEffectAnswerState : uint8 +{ + // Effect is not answered yet + Pending UMETA(DisplayName="Pending"), + // Effect reach out of prediction windows, was cancelled but can be re-applied + Timeout UMETA(DisplayName="Timeout"), + // Effect was answered and applied + Validated UMETA(DisplayName="Accepted") +}; + class UGMCAbility; UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent, DisplayName="GMC Ability System Component"), meta=(Categories="GMAS")) @@ -126,6 +139,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Return the active ability effects TMap GetActiveEffects() const { return ActiveEffects; } + UGMCAbilityEffect* GetActiveEffectByHandle(int EffectID) const; + // Return active Effect with tag // Match exact doesn't look for depth in the tag, it will only match the exact tag UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Abilities") @@ -202,10 +217,19 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Do not call directly on client, go through QueueAbility. Can be used to call server-side abilities (like AI). bool TryActivateAbility(TSubclassOf ActivatedAbility, const UInputAction* InputAction = nullptr, const FGameplayTag ActivationTag = FGameplayTag::EmptyTag); - - // Queue an ability to be executed + + + /** + * Queue an ability for activation based on the provided input tag and action. + * + * @param InputTag The gameplay tag associated with the ability to queue. + * @param InputAction The input action triggering the ability. + * @param bPreventConcurrentActivation If true, prevents the concurrent activation of abilities already in progress. The check is made locally + * reducing server charge, but also required in case of activation key wait inside the ability. + */ UFUNCTION(BlueprintCallable, DisplayName="Activate Ability", Category="GMAS|Abilities") - void QueueAbility(UPARAM(meta=(Categories="Input"))FGameplayTag InputTag, const UInputAction* InputAction = nullptr); + void QueueAbility(UPARAM(meta=(Categories="Input")) + FGameplayTag InputTag, const UInputAction* InputAction = nullptr, bool bPreventConcurrentActivation = false); UFUNCTION(BlueprintCallable, DisplayName="Count Queued Ability Instances", Category="GMAS|Abilities") int32 GetQueuedAbilityCount(FGameplayTag AbilityTag); @@ -303,15 +327,23 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo * Applies an effect to the ability component. If the Queue Type is Predicted, the effect will be immediately added * on both client and server; this must happen within the GMC movement lifecycle for it to be valid. If the * Queue Type is anything else, the effect must be queued on the server and will be replicated to the client. + * @param HandlingAbility : Optional ability handling, if provided, the end ability will trigger also automatically the end of the effect. */ UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Apply Ability Effect") void ApplyAbilityEffectSafe(TSubclassOf EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, - UPARAM(DisplayName="Success") bool& OutSuccess, UPARAM(DisplayName="Effect Handle") int& OutEffectHandle, UPARAM(DisplayName="Effect Network ID") int& OutEffectId, UPARAM(DisplayName="Effect Instance") UGMCAbilityEffect*& OutEffect); + UPARAM(DisplayName="Success") + bool& OutSuccess, + UPARAM(DisplayName="Effect Handle") int& OutEffectHandle, + UPARAM(DisplayName="Effect Network ID") int& OutEffectId, + UPARAM(DisplayName="Effect Instance") UGMCAbilityEffect*& OutEffect, + UPARAM(DisplayName="(Opt) Ability Handling") UGMCAbility* HandlingAbility = nullptr); /** Short version of ApplyAbilityEffect (Fire and Forget, return nullptr if fail, or the effect instance if success) * Don't suggest it for BP user to avoid confusion. + * @param HandlingAbility : Optional ability handling, if provided, the end ability will trigger al */ - UGMCAbilityEffect* ApplyAbilityEffectShort(TSubclassOf EffectClass, EGMCAbilityEffectQueueType QueueType); + UGMCAbilityEffect* ApplyAbilityEffectShort(TSubclassOf EffectClass, EGMCAbilityEffectQueueType QueueType, + UPARAM(DisplayName="(Opt) Ability Handling") UGMCAbility* HandlingAbility = nullptr); /** * Applies an effect to the ability component. If the Queue Type is Predicted, the effect will be immediately added @@ -347,6 +379,9 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(BlueprintCallable, Category="GMAS|Effects") void RemoveActiveAbilityEffect(UGMCAbilityEffect* Effect); + UFUNCTION(BlueprintCallable, Category="GMAS|Effects") + void RemoveActiveAbilityEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); + UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Active Ability Effect (Safe)") void RemoveActiveAbilityEffectSafe(UGMCAbilityEffect* Effect, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted); @@ -406,6 +441,11 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Called during the Ancillary Tick UPROPERTY(BlueprintAssignable) FOnAncillaryTick OnAncillaryTick; + + // Called when a task times out + UPROPERTY(BlueprintAssignable) + FOnTaskTimeout OnTaskTimeout; + //// // Called when the set of active tags changes. @@ -435,14 +475,18 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo TArray GetAllAttributes() const; /** Get an Attribute using its Tag */ - const FAttribute* GetAttributeByTag(FGameplayTag AttributeTag) const; + const FAttribute* GetAttributeByTag(UPARAM(meta=(Categories="Attribute")) FGameplayTag AttributeTag) const; TMap GetActiveAbilities() const { return ActiveAbilities; } - // Get Attribute value by Tag + // Get Attribute value (RawValue + Temporal Modifiers) by Tag UFUNCTION(BlueprintPure, Category="GMAS|Attributes") float GetAttributeValueByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; + // Get Attribute Value without Temporal Modifiers + UFUNCTION(BlueprintPure, Category="GMAS|Attributes") + float GetAttributeRawValue(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; + // Get Attribute value by Tag UFUNCTION(BlueprintPure, Category="GMAS|Attributes") FAttributeClamp GetAttributeClampByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; @@ -450,16 +494,16 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Set Attribute value by Tag // Will NOT trigger an "OnAttributeChanged" Event // bResetModifiers: Will reset all modifiers on the attribute to the base value. DO NOT USE if you have any active effects that modify this attribute. - UFUNCTION(BlueprintCallable, Category="GMAS|Attributes") + UFUNCTION(BlueprintCallable, Category="GMAS|Attributes", meta=(DeprecatedFunction, DeprecationMessage="Please use ApplyAbilityAttributeModifier instead.")) bool SetAttributeValueByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag, float NewValue, bool bResetModifiers = false); /** Get the default value of an attribute from the data assets. */ UFUNCTION(BlueprintCallable, Category="GMAS|Attributes") - float GetBaseAttributeValueByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; + float GetAttributeInitialValueByTag(UPARAM(meta=(Categories="Attribute"))FGameplayTag AttributeTag) const; // Apply modifiers that affect attributes UFUNCTION(BlueprintCallable, Category="GMAS|Attributes") - void ApplyAbilityEffectModifier(FGMCAttributeModifier AttributeModifier,bool bModifyBaseValue, bool bNegateValue = false, UGMC_AbilitySystemComponent* SourceAbilityComponent = nullptr); + void ApplyAbilityAttributeModifier(const FGMCAttributeModifier& AttributeModifier); UPROPERTY(BlueprintReadWrite, Category = "GMCAbilitySystem") bool bJustTeleported; @@ -586,14 +630,24 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo public: // Empty the AbilityMap and remove all granted abilities from existing maps - UFUNCTION(BlueprintCallable) + UFUNCTION(BlueprintCallable, Category="GMAS|Abilities") void ClearAbilityMap(); + virtual void SetAttributeInitialValue(const FGameplayTag& AttributeTag, float& BaseValue); + + UFUNCTION(BlueprintImplementableEvent, Category="GMAS|Abilities") + void OnInitializeAttributeInitialValue(const FGameplayTag& AttributeTag, float& BaseValue); + private: // List of filtered tag delegates to call when tags change. TArray> FilteredTagDelegates; FGameplayAttributeChangedNative NativeAttributeChangeDelegate; + + // Will calculate and process stack of attributes + void ProcessAttributes(bool bInGenPredictionTick); + + TArray ModifierHistory; // Get the map from the data asset and apply that to the component's map void InitializeAbilityMap(); @@ -666,6 +720,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // This must run before variable binding void InstantiateAttributes(); + + void SetStartingTags(); // Check if ActiveTags has changed and call delegates @@ -682,6 +738,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // Tick Predicted and Active Effects void TickActiveEffects(float DeltaTime); + // Tick active abilities, primarily the Tasks inside them void TickActiveAbilities(float DeltaTime); @@ -740,7 +797,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo // This need to be persisted for a while // This never empties out so it'll infinitely grow, probably a better way to accomplish this UPROPERTY() - TMap ProcessedEffectIDs; + TMap ProcessedEffectIDs; // Let the client know that the server has activated this ability as well // Needed for the client to cancel mis-predicted abilities @@ -793,5 +850,5 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo UFUNCTION(NetMulticast, Unreliable) void MC_SpawnSound(USoundBase* Sound, FVector Location, float VolumeMultiplier = 1.f, float PitchMultiplier = 1.f, bool bIsClientPredicted = false); - + friend class FGameplayDebuggerCategory_GMCAbilitySystem; }; diff --git a/Source/GMCAbilitySystem/Public/Debug/GameplayDebuggerCategory_GMCAbilitySystem.h b/Source/GMCAbilitySystem/Public/Debug/GameplayDebuggerCategory_GMCAbilitySystem.h index 2252dcfe..ae373ce0 100644 --- a/Source/GMCAbilitySystem/Public/Debug/GameplayDebuggerCategory_GMCAbilitySystem.h +++ b/Source/GMCAbilitySystem/Public/Debug/GameplayDebuggerCategory_GMCAbilitySystem.h @@ -25,11 +25,17 @@ class GMCABILITYSYSTEM_API FGameplayDebuggerCategory_GMCAbilitySystem : public F // Put all data you want to display here FString ActorName; FString GrantedAbilities; + int NBGrantedAbilities; FString ActiveTags; + int NBActiveTags; FString Attributes; + int NBAttributes; FString ActiveEffects; + int NBActiveEffects; FString ActiveEffectData; + int NBActiveEffectData; FString ActiveAbilities; + int NBActiveAbilities; void Serialize(FArchive& Ar); }; diff --git a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h index 374e338b..7ea1552a 100644 --- a/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h +++ b/Source/GMCAbilitySystem/Public/Effects/GMCAbilityEffect.h @@ -6,7 +6,7 @@ #include "GameplayTagContainer.h" #include "UObject/Object.h" #include "GMCAbilitySystem.h" -#include "Attributes/GMCAttributeModifier.h" +#include "GMCAttributeModifier.h" #include "GMCAbilityEffect.generated.h" class UGMC_AbilitySystemComponent; @@ -14,9 +14,10 @@ class UGMC_AbilitySystemComponent; UENUM(BlueprintType) enum class EGMASEffectType : uint8 { - Instant, // Applies Instantly - Duration, // Lasts for X time - Infinite // Lasts forever + Instant UMETA(DisplayName = "Instant", ToolTip = "Effect applies instantly, Apply fully and immediatly die, no tick"), + Ticking UMETA(DisplayName = "Ticking", ToolTip = "Effect applies periodically, Apply each tick multiplied by DeltaTime (Modifiers is Amount per second), and die after Duration or Removal"), + Persistent UMETA(DisplayName = "Persistent", ToolTip = "Effect applies persistently, Apply fully immediatly, and die after Duration or Removal"), + Periodic UMETA(DisplayName = "Periodic", ToolTip = "Effect applies periodically, Each interval applu fully the modifier value. Ticking. Die after duration of Removal"), }; UENUM(BlueprintType) @@ -27,6 +28,8 @@ enum class EGMASEffectState : uint8 Ended // Lasts forever }; + + // Container for exposing the attribute modifier to blueprints UCLASS() class GMCABILITYSYSTEM_API UGMCAttributeModifierContainer : public UObject @@ -60,6 +63,9 @@ struct FGMCAbilityEffectData UPROPERTY() int EffectID; + UPROPERTY() + uint8 bServerAuth : 1 {false}; // The server will never be acknowledge/predicted + UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") double StartTime; @@ -69,38 +75,32 @@ struct FGMCAbilityEffectData UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") double CurrentDuration{0.f}; - // Instantly applies effect then exits. Will not tick. - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") - bool bIsInstant = true; - + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + EGMASEffectType EffectType = EGMASEffectType::Instant; + // Apply an inversed version of the modifiers at effect end - UPROPERTY() + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", meta=(EditCondition = "EffectType == EGMASEffectType::Ticking || EffectType == EGMASEffectType::Persistent || EffectType == EGMASEffectType::Periodic", EditConditionHides)) bool bNegateEffectAtEnd = false; // Delay before the effect starts UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") double Delay = 0; + // Periodic will tick immediatly if true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem" , meta=(EditCondition = "EffectType == EGMASEffectType::Periodic", EditConditionHides)) + bool bPeriodicFirstTick = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem" , meta=(EditCondition = "EffectType == EGMASEffectType::Periodic", EditConditionHides, ClampMin = "0.1", UIMin = "0.1")) + float PeriodicInterval = 1.f; + // How long the effect lasts, 0 for infinite // Does nothing if effect is instant - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem", meta=(EditCondition = "EffectType == EGMASEffectType::Ticking || EffectType == EGMASEffectType::Persistent || EffectType == EGMASEffectType::Periodic", EditConditionHides)) double Duration = 0; - - // How often the periodic effect ticks - // Suggest keeping this above .01 - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") - double Period = 0; - - // For Period effects, whether first tick should happen immediately - UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem") - bool bPeriodTickAtStart = false; - + // Time in seconds that the client has to apply itself an external effect before the server will force it. If this time is reach, a rollback is likely to happen. UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GMCAbilitySystem", AdvancedDisplay) float ClientGraceTime = 1.f; - - UPROPERTY() - int LateApplicationID = -1; // Tag to identify this effect UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") @@ -133,9 +133,9 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") FGameplayTagContainer GrantedAbilities; - // If tag is present, periodic effect will not tick. Duration is not affected. + // If tag is present, this effect will not tick. Duration is not affected. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") - FGameplayTagContainer PausePeriodicEffect; + FGameplayTagContainer PauseEffect; // On activation, will end ability present in this container UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem") @@ -161,7 +161,7 @@ struct FGMCAbilityEffectData } FString ToString() const{ - return FString::Printf(TEXT("[id: %d] [Tag: %s] (Duration: %.3lf) (CurrentDuration: %.3lf)"), EffectID, *EffectTag.ToString(), Duration, CurrentDuration); + return FString::Printf(TEXT("[id: %d] [Tag: %s] (Dur: %.3lf) (CurDur: %.3lf)"), EffectID, *EffectTag.ToString(), Duration, CurrentDuration); } // query stuff @@ -184,7 +184,6 @@ struct FGMCAbilityEffectData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMCAbilitySystem", meta = (DisplayName = "End Ability On End Via Definition Query")) // end ability on effect end if definition matches query FGameplayTagQuery EndAbilityOnEndQuery; - }; /** @@ -197,6 +196,11 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject { GENERATED_BODY() +#if WITH_EDITOR + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + public: EGMASEffectState CurrentState; @@ -213,6 +217,8 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject virtual void Tick(float DeltaTime); + int32 CalculatePeriodicTicksBetween(float Period, float StartActionTimer, float EndActionTimer); + // Return the current duration of the effect UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMAS|Effects") float GetCurrentDuration() const { return EffectData.CurrentDuration; } @@ -244,20 +250,28 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject void UpdateState(EGMASEffectState State, bool Force=false); - virtual bool IsPeriodPaused(); + virtual bool IsPaused(); + + bool IsEffectModifiersRegisterInHistory() const; + float ProcessCustomModifier(const TSubclassOf& MCClass, const FAttribute* attribute); + bool bCompleted; // Time that the client applied this Effect. Used for when a client predicts an effect, if the server has not // confirmed this effect within a time range, the effect will be cancelled. float ClientEffectApplicationTime; - UFUNCTION(BlueprintPure) + UFUNCTION(BlueprintPure, Category = "GMCAbilitySystem") void GetOwnerActor(AActor*& OwnerActor) const; + UFUNCTION(BlueprintPure, Category = "GMCAbilitySystem") + UGMC_AbilitySystemComponent* GetOwnerAbilityComponent() const { return OwnerAbilityComponent; } + protected: - UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") - UGMC_AbilitySystemComponent* SourceAbilityComponent = nullptr; + + UPROPERTY(Transient) + TMap, UGMCAttributeModifierCustom_Base*> CustomModifiersInstances; UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem") UGMC_AbilitySystemComponent* OwnerAbilityComponent = nullptr; @@ -268,9 +282,6 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject private: bool bHasStarted; bool bHasAppliedEffect; - - // Used for calculating when to tick Period effects - float PrevPeriodMod = 0; void CheckState(); @@ -283,7 +294,6 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject void AddAbilitiesToOwner(); void RemoveAbilitiesFromOwner(); void EndActiveAbilitiesFromOwner(const FGameplayTagContainer& TagContainer); - // Does the owner have any of the tags from the container? bool DoesOwnerHaveTagFromContainer(FGameplayTagContainer& TagContainer) const; @@ -304,7 +314,7 @@ class GMCABILITYSYSTEM_API UGMCAbilityEffect : public UObject FString ToString() { - return FString::Printf(TEXT("[name: %s] (State %s) | Started: %d | Period Paused: %d | Data: %s"), *GetName(), *EnumToString(CurrentState), bHasStarted, IsPeriodPaused(), *EffectData.ToString()); + return FString::Printf(TEXT("[name: %s] (%s) | %s | %s | Data: %s"), *GetName().Right(30), *EnumToString(CurrentState), bHasStarted ? TEXT("Started") : TEXT("Not Started"), IsPaused() ? TEXT("Paused") : TEXT("Running"), *EffectData.ToString()); } UFUNCTION(BlueprintCallable, Category = "GMCAbilitySystem|Effects|Queries")