第一次提交
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user