Implemented OLSCollisionChannels. Implemented OLSGameplayEffectContext. Implemented OLSPhysicalMaterialWithTags.
551 lines
18 KiB
C++
551 lines
18 KiB
C++
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
|
|
|
|
|
#include "AbilitySystem/Abilities/OLSGameplayAbility.h"
|
|
|
|
#include "OLSLog.h"
|
|
#include "AbilitySystem/OLSAbilitySimpleFailureMessage.h"
|
|
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
|
#include "AbilitySystem/Abilities/OLSAbilityCost.h"
|
|
#include "AbilitySystemBlueprintLibrary.h"
|
|
#include "AbilitySystemGlobals.h"
|
|
#include "AbilitySystem/OLSGameplayEffectContext.h"
|
|
#include "AbilitySystem/Interfaces/OLSAbilitySourceInterface.h"
|
|
#include "Components/OLSHeroComponent.h"
|
|
#include "GameFramework/GameplayMessageSubsystem.h"
|
|
#include "Physics/OLSPhysicalMaterialWithTags.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameplayAbility)
|
|
|
|
DEFINE_LOG_CATEGORY(LogOLSGameplayAbility);
|
|
|
|
#define ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(FunctionName, ReturnValue) \
|
|
{ \
|
|
if (!ensure(IsInstantiated())) \
|
|
{ \
|
|
OLS_LOG(LogOLSGameplayAbility, Error, TEXT("%s: " #FunctionName " cannot be called on a non-instanced ability. Check the instancing policy."), *GetPathName()); \
|
|
return ReturnValue; \
|
|
} \
|
|
}
|
|
|
|
UE_DEFINE_GAMEPLAY_TAG(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE, "Ability.UserFacingSimpleActivateFail.Message");
|
|
UE_DEFINE_GAMEPLAY_TAG(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, "Ability.PlayMontageOnActivateFail.Message");
|
|
|
|
UOLSGameplayAbility::UOLSGameplayAbility(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
|
{
|
|
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo;
|
|
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
|
|
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
|
|
NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer;
|
|
|
|
ActivationPolicy = EOLSAbilityActivationPolicy::OnInputTriggered;
|
|
ActivationGroup = EOLSAbilityActivationGroup::Independent;
|
|
|
|
bShouldLogCancellation = false;
|
|
|
|
// @TODO: Implement OLSCameraMode.
|
|
// ActiveCameraMode = nullptr;
|
|
}
|
|
|
|
bool UOLSGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle handle,
|
|
const FGameplayAbilityActorInfo* actorInfo,
|
|
const FGameplayTagContainer* sourceTags,
|
|
const FGameplayTagContainer* targetTags,
|
|
FGameplayTagContainer* optionalRelevantTags) const
|
|
{
|
|
if (!actorInfo || !actorInfo->AbilitySystemComponent.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!Super::CanActivateAbility(handle, actorInfo, sourceTags, targetTags, optionalRelevantTags))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//@TODO Possibly remove after setting up tag relationships
|
|
UOLSAbilitySystemComponent* asc = CastChecked<UOLSAbilitySystemComponent>(actorInfo->AbilitySystemComponent.Get());
|
|
if (asc->IsActivationGroupBlocked(ActivationGroup))
|
|
{
|
|
if (optionalRelevantTags)
|
|
{
|
|
// @TODO: Implement LyraGameplayTags::Ability_ActivateFail_ActivationGroup.
|
|
// optionalRelevantTags->AddTag(LyraGameplayTags::Ability_ActivateFail_ActivationGroup);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UOLSGameplayAbility::SetCanBeCanceled(bool canBeCanceled)
|
|
{
|
|
// The ability can not block canceling if it's replaceable.
|
|
if (!canBeCanceled && (ActivationGroup == EOLSAbilityActivationGroup::Exclusive_Replaceable))
|
|
{
|
|
OLS_LOG(LogOLSGameplayAbility, Error,
|
|
TEXT(
|
|
"Ability [%s] can not block canceling because its activation group is replaceable."
|
|
), *GetName());
|
|
return;
|
|
}
|
|
|
|
Super::SetCanBeCanceled(canBeCanceled);
|
|
}
|
|
|
|
void UOLSGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec)
|
|
{
|
|
Super::OnGiveAbility(actorInfo, spec);
|
|
|
|
K2_OnAbilityAdded();
|
|
|
|
TryActivateAbilityOnSpawn(actorInfo, spec);
|
|
}
|
|
|
|
void UOLSGameplayAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec)
|
|
{
|
|
K2_OnAbilityRemoved();
|
|
|
|
Super::OnRemoveAbility(actorInfo, spec);
|
|
}
|
|
|
|
void UOLSGameplayAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
|
|
const FGameplayAbilityActorInfo* ActorInfo,
|
|
const FGameplayAbilityActivationInfo ActivationInfo,
|
|
const FGameplayEventData* TriggerEventData)
|
|
{
|
|
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
|
|
}
|
|
|
|
void UOLSGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle,
|
|
const FGameplayAbilityActorInfo* ActorInfo,
|
|
const FGameplayAbilityActivationInfo ActivationInfo,
|
|
bool bReplicateEndAbility, bool bWasCancelled)
|
|
{
|
|
// @TODO: Implement UOLSCameraMode.
|
|
// ClearCameraMode();
|
|
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
|
|
}
|
|
|
|
bool UOLSGameplayAbility::CheckCost(const FGameplayAbilitySpecHandle handle,
|
|
const FGameplayAbilityActorInfo* actorInfo,
|
|
FGameplayTagContainer* optionalRelevantTags) const
|
|
{
|
|
if (!Super::CheckCost(handle, actorInfo, optionalRelevantTags) || !actorInfo)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Verify we can afford any additional costs
|
|
for (const TObjectPtr<UOLSAbilityCost>& additionalCost : AdditionalCosts)
|
|
{
|
|
if (additionalCost)
|
|
{
|
|
if (!additionalCost->CheckCost(this, handle, actorInfo, /*inout*/ optionalRelevantTags))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UOLSGameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle handle,
|
|
const FGameplayAbilityActorInfo* actorInfo,
|
|
const FGameplayAbilityActivationInfo activationInfo) const
|
|
{
|
|
check(actorInfo);
|
|
|
|
// Used to determine if the ability actually hit a target (as some costs are only spent on successful attempts)
|
|
auto determineIfAbilityHitTarget = [&]()
|
|
{
|
|
if (actorInfo->IsNetAuthority())
|
|
{
|
|
if (UOLSAbilitySystemComponent* ASC = Cast<UOLSAbilitySystemComponent>(actorInfo->AbilitySystemComponent.Get()))
|
|
{
|
|
FGameplayAbilityTargetDataHandle targetData;
|
|
ASC->GetAbilityTargetData(handle, activationInfo, targetData);
|
|
for (int32 targetDataIdx = 0; targetDataIdx < targetData.Data.Num(); ++targetDataIdx)
|
|
{
|
|
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(targetData, targetDataIdx))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Pay any additional costs
|
|
bool hasAbilityHitTarget = false;
|
|
bool hasDeterminedIfAbilityHitTarget = false;
|
|
for (const TObjectPtr<UOLSAbilityCost>& additionalCost : AdditionalCosts)
|
|
{
|
|
if (additionalCost)
|
|
{
|
|
if (additionalCost->ShouldOnlyApplyCostOnHit())
|
|
{
|
|
if (!hasDeterminedIfAbilityHitTarget)
|
|
{
|
|
hasAbilityHitTarget = determineIfAbilityHitTarget();
|
|
hasDeterminedIfAbilityHitTarget = true;
|
|
}
|
|
|
|
if (!hasAbilityHitTarget)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
additionalCost->ApplyCost(this, handle, actorInfo, activationInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
FGameplayEffectContextHandle UOLSGameplayAbility::MakeEffectContext(const FGameplayAbilitySpecHandle handle,
|
|
const FGameplayAbilityActorInfo* actorInfo) const
|
|
{
|
|
FGameplayEffectContextHandle contextHandle = Super::MakeEffectContext(handle, actorInfo);
|
|
|
|
FOLSGameplayEffectContext* effectContext = FOLSGameplayEffectContext::ExtractEffectContext(contextHandle);
|
|
check(effectContext);
|
|
|
|
check(actorInfo);
|
|
|
|
AActor* effectCauser = nullptr;
|
|
const IOLSAbilitySourceInterface* abilitySource = nullptr;
|
|
float sourceLevel = 0.0f;
|
|
GetAbilitySource(handle, actorInfo, /*out*/ sourceLevel, /*out*/ abilitySource, /*out*/ effectCauser);
|
|
|
|
UObject* sourceObject = GetSourceObject(handle, actorInfo);
|
|
|
|
AActor* instigator = actorInfo ? actorInfo->OwnerActor.Get() : nullptr;
|
|
|
|
effectContext->SetAbilitySource(abilitySource, sourceLevel);
|
|
effectContext->AddInstigator(instigator, effectCauser);
|
|
effectContext->AddSourceObject(sourceObject);
|
|
|
|
return contextHandle;
|
|
}
|
|
|
|
void UOLSGameplayAbility::ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& spec,
|
|
FGameplayAbilitySpec* abilitySpec) const
|
|
{
|
|
Super::ApplyAbilityTagsToGameplayEffectSpec(spec, abilitySpec);
|
|
|
|
if (const FHitResult* hitResult = spec.GetContext().GetHitResult())
|
|
{
|
|
if (const UOLSPhysicalMaterialWithTags* physMatWithTags = Cast<const UOLSPhysicalMaterialWithTags>(hitResult->PhysMaterial.Get()))
|
|
{
|
|
spec.CapturedTargetTags.GetSpecTags().AppendTags(physMatWithTags->Tags);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UOLSGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& abilitySystemComponent,
|
|
const FGameplayTagContainer* sourceTags,
|
|
const FGameplayTagContainer* targetTags,
|
|
FGameplayTagContainer* optionalRelevantTags) const
|
|
{
|
|
// Specialized version to handle death exclusion and AbilityTags expansion via ASC
|
|
|
|
bool isBlocked = false;
|
|
bool isMissing = false;
|
|
|
|
UAbilitySystemGlobals& abilitySystemGlobals = UAbilitySystemGlobals::Get();
|
|
const FGameplayTag& blockedTag = abilitySystemGlobals.ActivateFailTagsBlockedTag;
|
|
const FGameplayTag& missingTag = abilitySystemGlobals.ActivateFailTagsMissingTag;
|
|
|
|
// Check if any of this ability's tags are currently blocked
|
|
if (abilitySystemComponent.AreAbilityTagsBlocked(GetAssetTags()))
|
|
{
|
|
isBlocked = true;
|
|
}
|
|
|
|
const UOLSAbilitySystemComponent* asc = Cast<UOLSAbilitySystemComponent>(&abilitySystemComponent);
|
|
static FGameplayTagContainer allRequiredTags;
|
|
static FGameplayTagContainer allBlockedTags;
|
|
|
|
allRequiredTags = ActivationRequiredTags;
|
|
allBlockedTags = ActivationBlockedTags;
|
|
|
|
// Expand our ability tags to add additional required/blocked tags
|
|
if (asc)
|
|
{
|
|
asc->GetAdditionalActivationTagRequirements(GetAssetTags(), allRequiredTags, allBlockedTags);
|
|
}
|
|
|
|
// Check to see the required/blocked tags for this ability
|
|
if (allBlockedTags.Num() || allRequiredTags.Num())
|
|
{
|
|
static FGameplayTagContainer abilitySystemComponentTags;
|
|
|
|
abilitySystemComponentTags.Reset();
|
|
abilitySystemComponent.GetOwnedGameplayTags(abilitySystemComponentTags);
|
|
|
|
if (abilitySystemComponentTags.HasAny(allBlockedTags))
|
|
{
|
|
// @TODO: Implement LyraGameplayTags::Status_Death.
|
|
// @TODO: Implement LyraGameplayTags::Ability_ActivateFail_IsDead.
|
|
// if (optionalRelevantTags && abilitySystemComponentTags.HasTag(LyraGameplayTags::Status_Death))
|
|
// {
|
|
// // If player is dead and was rejected due to blocking tags, give that feedback
|
|
// optionalRelevantTags->AddTag(LyraGameplayTags::Ability_ActivateFail_IsDead);
|
|
// }
|
|
|
|
isBlocked = true;
|
|
}
|
|
|
|
if (!abilitySystemComponentTags.HasAll(allRequiredTags))
|
|
{
|
|
isMissing = true;
|
|
}
|
|
}
|
|
|
|
if (sourceTags != nullptr)
|
|
{
|
|
if (SourceBlockedTags.Num() || SourceRequiredTags.Num())
|
|
{
|
|
if (sourceTags->HasAny(SourceBlockedTags))
|
|
{
|
|
isBlocked = true;
|
|
}
|
|
|
|
if (!sourceTags->HasAll(SourceRequiredTags))
|
|
{
|
|
isMissing = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (targetTags != nullptr)
|
|
{
|
|
if (TargetBlockedTags.Num() || TargetRequiredTags.Num())
|
|
{
|
|
if (targetTags->HasAny(TargetBlockedTags))
|
|
{
|
|
isBlocked = true;
|
|
}
|
|
|
|
if (!targetTags->HasAll(TargetRequiredTags))
|
|
{
|
|
isMissing = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isBlocked)
|
|
{
|
|
if (optionalRelevantTags && blockedTag.IsValid())
|
|
{
|
|
optionalRelevantTags->AddTag(blockedTag);
|
|
}
|
|
return false;
|
|
}
|
|
if (isMissing)
|
|
{
|
|
if (optionalRelevantTags && missingTag.IsValid())
|
|
{
|
|
optionalRelevantTags->AddTag(missingTag);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
UOLSAbilitySystemComponent* UOLSGameplayAbility::GetOLSAbilitySystemComponentFromActorInfo() const
|
|
{
|
|
return (CurrentActorInfo ? Cast<UOLSAbilitySystemComponent>(CurrentActorInfo->AbilitySystemComponent.Get()) : nullptr);
|
|
}
|
|
|
|
AController* UOLSGameplayAbility::GetControllerFromActorInfo() const
|
|
{
|
|
if (CurrentActorInfo)
|
|
{
|
|
if (AController* controller = CurrentActorInfo->PlayerController.Get())
|
|
{
|
|
return controller;
|
|
}
|
|
|
|
// Look for a player controller or pawn in the owner chain.
|
|
AActor* testActor = CurrentActorInfo->OwnerActor.Get();
|
|
while (testActor)
|
|
{
|
|
if (AController* controller = Cast<AController>(testActor))
|
|
{
|
|
return controller;
|
|
}
|
|
|
|
if (APawn* pawn = Cast<APawn>(testActor))
|
|
{
|
|
return pawn->GetController();
|
|
}
|
|
|
|
testActor = testActor->GetOwner();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UOLSHeroComponent* UOLSGameplayAbility::GetHeroComponentFromActorInfo() const
|
|
{
|
|
return (CurrentActorInfo ? UOLSHeroComponent::FindHeroComponent(CurrentActorInfo->AvatarActor.Get()) : nullptr);
|
|
}
|
|
|
|
bool UOLSGameplayAbility::CanChangeActivationGroup(EOLSAbilityActivationGroup newGroup) const
|
|
{
|
|
if (!IsInstantiated() || !IsActive())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ActivationGroup == newGroup)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponentFromActorInfo();
|
|
check(asc);
|
|
|
|
if ((ActivationGroup != EOLSAbilityActivationGroup::Exclusive_Blocking) && asc->IsActivationGroupBlocked(newGroup))
|
|
{
|
|
// This ability can't change groups if it's blocked (unless it is the one doing the blocking).
|
|
return false;
|
|
}
|
|
|
|
if ((newGroup == EOLSAbilityActivationGroup::Exclusive_Replaceable) && !CanBeCanceled())
|
|
{
|
|
// This ability can't become replaceable if it can't be canceled.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UOLSGameplayAbility::ChangeActivationGroup(EOLSAbilityActivationGroup newGroup)
|
|
{
|
|
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ChangeActivationGroup, false);
|
|
|
|
if (!CanChangeActivationGroup(newGroup))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ActivationGroup != newGroup)
|
|
{
|
|
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponentFromActorInfo();
|
|
check(asc);
|
|
|
|
asc->RemoveAbilityFromActivationGroup(ActivationGroup, this);
|
|
asc->AddAbilityToActivationGroup(newGroup, this);
|
|
|
|
ActivationGroup = newGroup;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
EOLSAbilityActivationPolicy UOLSGameplayAbility::GetActivationPolicy() const
|
|
{
|
|
return ActivationPolicy;
|
|
}
|
|
|
|
EOLSAbilityActivationGroup UOLSGameplayAbility::GetActivationGroup() const
|
|
{
|
|
return ActivationGroup;
|
|
}
|
|
|
|
void UOLSGameplayAbility::TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* actorInfo,
|
|
const FGameplayAbilitySpec& spec) const
|
|
{
|
|
// Try to activate if activation policy is on spawn.
|
|
if (actorInfo && !spec.IsActive() && (ActivationPolicy == EOLSAbilityActivationPolicy::OnSpawn))
|
|
{
|
|
UAbilitySystemComponent* asc = actorInfo->AbilitySystemComponent.Get();
|
|
const AActor* avatarActor = actorInfo->AvatarActor.Get();
|
|
|
|
// If avatar actor is torn off or about to die, don't try to activate until we get the new one.
|
|
if (asc && avatarActor && !avatarActor->GetTearOff() && (avatarActor->GetLifeSpan() <= 0.0f))
|
|
{
|
|
const bool isLocalExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly);
|
|
const bool iServerExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerInitiated);
|
|
|
|
const bool shouldClientActivate = actorInfo->IsLocallyControlled() && isLocalExecution;
|
|
const bool shouldServerActivate = actorInfo->IsNetAuthority() && iServerExecution;
|
|
|
|
if (shouldClientActivate || shouldServerActivate)
|
|
{
|
|
asc->TryActivateAbility(spec.Handle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UOLSGameplayAbility::OnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const
|
|
{
|
|
NativeOnAbilityFailedToActivate(failedReason);
|
|
K2_OnAbilityFailedToActivate(failedReason);
|
|
}
|
|
|
|
void UOLSGameplayAbility::OnPawnAvatarSet()
|
|
{
|
|
K2_OnPawnAvatarSet();
|
|
}
|
|
|
|
void UOLSGameplayAbility::GetAbilitySource(FGameplayAbilitySpecHandle handle,
|
|
const FGameplayAbilityActorInfo* actorInfo,
|
|
float& outSourceLevel,
|
|
const IOLSAbilitySourceInterface*& outAbilitySource,
|
|
AActor*& outEffectCauser) const
|
|
{
|
|
outSourceLevel = 0.0f;
|
|
outAbilitySource = nullptr;
|
|
outEffectCauser = nullptr;
|
|
|
|
outEffectCauser = actorInfo->AvatarActor.Get();
|
|
|
|
// If we were added by something that's an ability info source, use it
|
|
UObject* sourceObject = GetSourceObject(handle, actorInfo);
|
|
|
|
outAbilitySource = Cast<IOLSAbilitySourceInterface>(sourceObject);
|
|
}
|
|
|
|
void UOLSGameplayAbility::NativeOnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const
|
|
{
|
|
bool simpleFailureFound = false;
|
|
for (FGameplayTag reason : failedReason)
|
|
{
|
|
if (!simpleFailureFound)
|
|
{
|
|
if (const FText* pUserFacingMessage = FailureTagToUserFacingMessages.Find(reason))
|
|
{
|
|
FOLSAbilitySimpleFailureMessage message;
|
|
message.PlayerController = GetActorInfo().PlayerController.Get();
|
|
message.FailureTags = failedReason;
|
|
message.UserFacingReason = *pUserFacingMessage;
|
|
|
|
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
|
messageSystem.BroadcastMessage(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE, message);
|
|
simpleFailureFound = true;
|
|
}
|
|
}
|
|
|
|
if (UAnimMontage* montage = FailureTagToAnimMontage.FindRef(reason))
|
|
{
|
|
FOLSAbilityMontageFailureMessage message;
|
|
message.PlayerController = GetActorInfo().PlayerController.Get();
|
|
message.AvatarActor = GetActorInfo().AvatarActor.Get();
|
|
message.FailureTags = failedReason;
|
|
message.FailureMontage = montage;
|
|
|
|
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
|
messageSystem.BroadcastMessage(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, message);
|
|
}
|
|
}
|
|
}
|