358 lines
12 KiB
C++
358 lines
12 KiB
C++
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
|
|
|
|
|
#include "Abilities/GCS_ComboAbility.h"
|
|
|
|
#include "AbilitySystemComponent.h"
|
|
#include "GCS_CombatEntityInterface.h"
|
|
#include "GCS_CombatSystemComponent.h"
|
|
#include "GCS_LogChannels.h"
|
|
#include "GGA_GameplayTags.h"
|
|
#include "Abilities/Tasks/AbilityTask_WaitInputPress.h"
|
|
#include "Combo/GCS_ComboDefinition.h"
|
|
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
|
|
#include "Weapon/GCS_WeaponInterface.h"
|
|
|
|
UGCS_ComboAbility::UGCS_ComboAbility()
|
|
{
|
|
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateYes;
|
|
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
|
|
AbilityTags.AddTagFast(GGA_AbilityTraitTags::ActivationOnSpawn);
|
|
}
|
|
|
|
void UGCS_ComboAbility::PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
|
FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
|
|
{
|
|
Super::PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData);
|
|
}
|
|
|
|
void UGCS_ComboAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
|
const FGameplayEventData* TriggerEventData)
|
|
{
|
|
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
|
if (!CombatSys)
|
|
{
|
|
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, false);
|
|
return;
|
|
}
|
|
|
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
|
AbilityEndedDelegateHandle = ASC->OnAbilityEnded.AddUObject(this, &ThisClass::HandleAbilityEnd);
|
|
|
|
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
|
|
}
|
|
|
|
bool UGCS_ComboAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags,
|
|
const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
|
|
{
|
|
return Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
|
|
}
|
|
|
|
void UGCS_ComboAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
|
|
{
|
|
Super::OnGiveAbility(ActorInfo, Spec);
|
|
// GiveSubAbilities(Spec);
|
|
}
|
|
|
|
void UGCS_ComboAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
|
|
{
|
|
Super::OnRemoveAbility(ActorInfo, Spec);
|
|
// RemoveSubAbilities();
|
|
}
|
|
|
|
void UGCS_ComboAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility,
|
|
bool bWasCancelled)
|
|
{
|
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
|
|
|
if (IsValid(ASC) && AbilityEndedDelegateHandle.IsValid())
|
|
{
|
|
ASC->OnAbilityEnded.Remove(AbilityEndedDelegateHandle);
|
|
AbilityEndedDelegateHandle.Reset();
|
|
}
|
|
|
|
// final check to make current ability ends(no callback will fired.)!
|
|
if (CurrentAbility.IsValid() && !bCurrentAbilityEnded)
|
|
{
|
|
ASC->CancelAbilityHandle(CurrentAbility);
|
|
CurrentAbility = FGameplayAbilitySpecHandle();
|
|
CurrentAbilityClass = nullptr;
|
|
}
|
|
|
|
ResetCombo();
|
|
|
|
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
|
|
}
|
|
|
|
bool UGCS_ComboAbility::AllowAdvanceCombo_Implementation() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void UGCS_ComboAbility::StartCombo_Implementation(const FGameplayEventData& ComboEvent)
|
|
{
|
|
HandleComboExecution(ComboEvent);
|
|
}
|
|
|
|
void UGCS_ComboAbility::AdvanceCombo_Implementation(const FGameplayEventData& ComboEventData)
|
|
{
|
|
if (!AllowAdvanceCombo())
|
|
{
|
|
GCS_CLOG(Verbose, "Can't advanced combo due to AllowAdvanceCombo.")
|
|
return;
|
|
}
|
|
|
|
HandleComboExecution(ComboEventData);
|
|
}
|
|
|
|
void UGCS_ComboAbility::ResetCombo_Implementation()
|
|
{
|
|
DesiredComboStep = INDEX_NONE;
|
|
bCurrentAbilityEnded = false;
|
|
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
|
if (IsValid(CombatSys))
|
|
{
|
|
CombatSys->ResetComboState();
|
|
}
|
|
}
|
|
|
|
void UGCS_ComboAbility::HandleAbilityEnd(const FAbilityEndedData& AbilityEndedData)
|
|
{
|
|
//Already ends
|
|
if (bCurrentAbilityEnded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//no ability running.
|
|
if (!CurrentAbility.IsValid() || CurrentAbility != AbilityEndedData.AbilitySpecHandle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// GCS_CLOG(Verbose, "Current ability:%s ended. bWasCancelled:%d", *CurrentAbilityClass->GetName(), AbilityEndedData.bWasCancelled)
|
|
bCurrentAbilityEnded = true;
|
|
CurrentAbility = FGameplayAbilitySpecHandle();
|
|
CurrentAbilityClass = nullptr;
|
|
|
|
// No next combo.
|
|
if (DesiredComboStep == INDEX_NONE)
|
|
{
|
|
ResetCombo();
|
|
}
|
|
}
|
|
|
|
bool UGCS_ComboAbility::SelectComboDefinition(const FGameplayEventData& ComboEventData, int32 CurrentStep, FGCS_ComboDefinition& OutDefinition)
|
|
{
|
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
|
UObject* Combat = GetCombatEntityFromActorInfo();
|
|
if (!ASC || !Combat) return false;
|
|
|
|
TObjectPtr<const UDataTable> ComboDefinitionTable = IGCS_CombatEntityInterface::Execute_GetComboDefinitionTable(Combat);
|
|
if (ComboDefinitionTable == nullptr)
|
|
{
|
|
GCS_CLOG(Error, "No combo definition table found from combat interface:%s, check your GetComboDefinitionTable implementation!", *GetNameSafe(Combat));
|
|
return false;
|
|
}
|
|
|
|
FGameplayTagContainer CurrentTags;
|
|
ASC->GetOwnedGameplayTags(CurrentTags);
|
|
|
|
// Find best matching row in data table
|
|
for (const auto& RowPair : ComboDefinitionTable->GetRowMap())
|
|
{
|
|
const FGCS_ComboDefinition* Row = reinterpret_cast<const FGCS_ComboDefinition*>(RowPair.Value);
|
|
if (!Row || Row->AbilityClass.IsNull()) continue;
|
|
|
|
if (!Row->TagQuery.IsEmpty() && !Row->TagQuery.Matches(CurrentTags))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// skip if event tag doesn't match.
|
|
if (ComboEventData.EventTag.IsValid() && Row->EventTag.IsValid() && ComboEventData.EventTag != Row->EventTag)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// skip if event instigatorTags
|
|
if (!Row->EventInstigatorTagQuery.IsEmpty() && !Row->EventInstigatorTagQuery.Matches(ComboEventData.InstigatorTags))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (CurrentStep > 0 && Row->MinComboStep == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// skip if MinComboStep < CurrentStep(only for non resetting combo)
|
|
if (!Row->bResetComboStep && Row->MinComboStep > 0 && Row->MinComboStep < CurrentStep) continue;
|
|
|
|
// skip if ability class is invalid.
|
|
TSubclassOf<UGameplayAbility> TempAbilityClass = Row->AbilityClass.LoadSynchronous();
|
|
if (TempAbilityClass == nullptr)
|
|
{
|
|
GCS_CLOG(Verbose, "invalid ability class found on row name:%s", *RowPair.Key.ToString());
|
|
continue;
|
|
}
|
|
|
|
FGameplayAbilitySpecHandle FoundAbility;
|
|
if (!UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(ASC, FoundAbility, TempAbilityClass, GetCurrentSourceObject()))
|
|
{
|
|
GCS_CLOG(Verbose, "skipped ability of class(%s) as it is not exists on ASC.", *TempAbilityClass->GetName());
|
|
continue;
|
|
}
|
|
|
|
if (Row->bRunActivationTest)
|
|
{
|
|
FGameplayTagContainer RelevantTags;
|
|
if (!UGGA_AbilitySystemFunctionLibrary::CanActivateAbility(ASC, FoundAbility, RelevantTags))
|
|
{
|
|
if (Row->bAbortIfActivationTestFailed)
|
|
{
|
|
GCS_CLOG(Verbose, "Activation test failed for ability:%s, reason:%s. Combo will be aborted.", *TempAbilityClass->GetName(), *RelevantTags.ToString());
|
|
return false;
|
|
}
|
|
{
|
|
GCS_CLOG(Verbose, "skipped ability of class(%s) due to activation test failed, reason:%s", *TempAbilityClass->GetName(), *RelevantTags.ToString());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip if custom rule doesn't met.
|
|
if (!CanSelectedComboDefinition(ComboEventData, CurrentStep, OutDefinition))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GCS_CLOG(Verbose, "selected ability of class(%s) as next combo(%d).", *TempAbilityClass->GetName(), CurrentStep);
|
|
OutDefinition = *Row;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UGCS_ComboAbility::CanSelectedComboDefinition_Implementation(const FGameplayEventData& ComboEvent, int32 CurrentStep, const FGCS_ComboDefinition& ComboDefinition) const
|
|
{
|
|
// Example code.
|
|
// FYourCustomData Data = ComboDefinition.Extension.Get<FYourCustomData>();
|
|
// UObject* Weapon = IGCS_CombatInterface::Execute_GCS_GetWeapon(GetCombatInterfaceFromActorInfo(), nullptr);
|
|
// return IGCS_WeaponInterface::Execute_GetWeaponTags(Weapon).MatchesQuery(Data.WeaponTagRequirements);
|
|
return true;
|
|
}
|
|
|
|
void UGCS_ComboAbility::HandleComboExecution(const FGameplayEventData& ComboEventData)
|
|
{
|
|
if (!IsLocallyControlled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
|
if (!ASC) return;
|
|
|
|
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
|
int32 CurrentStep = CombatSys ? CombatSys->GetComboStep() : 0;
|
|
|
|
bool bStartingCombo = CurrentStep == 0;
|
|
|
|
FGCS_ComboDefinition ComboDefinition;
|
|
|
|
if (!SelectComboDefinition(ComboEventData, CurrentStep, ComboDefinition))
|
|
{
|
|
GCS_CLOG(Verbose, "No next combo definition with current combo step:%d", CurrentStep);
|
|
DesiredComboStep = INDEX_NONE; // mark for no next action.
|
|
return;
|
|
}
|
|
|
|
TSubclassOf<UGameplayAbility> FoundAbilityClass = ComboDefinition.AbilityClass.Get();
|
|
|
|
FGameplayAbilitySpecHandle FoundAbility;
|
|
UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(ASC, FoundAbility, FoundAbilityClass, GetCurrentSourceObject());
|
|
check(FoundAbility.IsValid())
|
|
|
|
bool bShouldResetComboStep = !bStartingCombo && ComboDefinition.MinComboStep > 0 && ComboDefinition.bResetComboStep;
|
|
|
|
// Mark the desired step.
|
|
DesiredComboStep = bShouldResetComboStep ? 0 : CombatSys->GetComboStep() + 1;
|
|
|
|
// Cancel current ability if still running.
|
|
if (CurrentAbility.IsValid() && !bCurrentAbilityEnded)
|
|
{
|
|
ASC->CancelAbilityHandle(CurrentAbility);
|
|
CurrentAbility = FGameplayAbilitySpecHandle();
|
|
CurrentAbilityClass = nullptr;
|
|
}
|
|
|
|
bCurrentAbilityEnded = false;
|
|
|
|
//Combo Ability本身是预测激活的.
|
|
if (ASC->TryActivateAbility(FoundAbility, true))
|
|
{
|
|
CurrentAbility = FoundAbility;
|
|
CurrentAbilityClass = FoundAbilityClass;
|
|
CombatSys->UpdateComboStep(DesiredComboStep);
|
|
GCS_CLOG(Verbose, "combo advanced from step %d to %d with ability:%s", CurrentStep, DesiredComboStep, *FoundAbilityClass->GetName());
|
|
DesiredComboStep = INDEX_NONE;
|
|
}
|
|
else
|
|
{
|
|
GCS_CLOG(Verbose, "combo reset as the new ability(%s) can't be activated when advancing from step %d to %d.", *FoundAbilityClass->GetName(), CurrentStep, DesiredComboStep);
|
|
ResetCombo();
|
|
}
|
|
}
|
|
|
|
// void UGCS_ComboAbility::GiveSubAbilities(const FGameplayAbilitySpec& CurrentSpec)
|
|
// {
|
|
// UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
|
// if (!IsValid(ASC) || !ASC->IsOwnerActorAuthoritative())
|
|
// {
|
|
// return;
|
|
// }
|
|
//
|
|
// // Find best matching row in data table
|
|
// for (const auto& RowPair : ComboDefinitionTable->GetRowMap())
|
|
// {
|
|
// const FGCS_ComboDefinition* Row = reinterpret_cast<const FGCS_ComboDefinition*>(RowPair.Value);
|
|
// if (!Row || Row->AbilityClass.IsNull()) continue;
|
|
// TSubclassOf<UGameplayAbility> AbilityClass = Row->AbilityClass.LoadSynchronous();
|
|
//
|
|
// if (FGameplayAbilitySpec* ExistingSpec = UGGA_AbilitySystemFunctionLibrary::FindAbilitySpecFromClass(ASC, AbilityClass, CurrentSpec.SourceObject.Get()))
|
|
// {
|
|
// GCS_CLOG(Warning, "Found existing ability with same class(%s) and source object(%s),skipped.", *AbilityClass->GetName(), *GetNameSafe(CurrentSpec.SourceObject.Get()))
|
|
// continue;
|
|
// }
|
|
//
|
|
// FGameplayAbilitySpec NewSpec = ASC->BuildAbilitySpecFromClass(AbilityClass, Row->MinComboStep > 0 ? Row->MinComboStep : 0);
|
|
// NewSpec.SourceObject = CurrentSpec.SourceObject;
|
|
// const FGameplayAbilitySpecHandle& AbilitySpecHandle = ASC->GiveAbility(NewSpec);
|
|
// if (AbilitySpecHandle.IsValid())
|
|
// {
|
|
// AvailableAbilities.Add(AbilitySpecHandle);
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// void UGCS_ComboAbility::RemoveSubAbilities()
|
|
// {
|
|
// UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
|
// if (!IsValid(ASC))
|
|
// {
|
|
// return;
|
|
// }
|
|
// for (FGameplayAbilitySpecHandle SubAbilityHandle : AvailableAbilities)
|
|
// {
|
|
// ASC->ClearAbility(SubAbilityHandle);
|
|
// }
|
|
// AvailableAbilities.Empty();
|
|
// }
|
|
|
|
#if WITH_EDITOR
|
|
EDataValidationResult UGCS_ComboAbility::IsDataValid(class FDataValidationContext& Context) const
|
|
{
|
|
return Super::IsDataValid(Context);
|
|
}
|
|
#endif
|