第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Abilities/GCS_CombatAbility.h"
#include "GCS_CombatSystemComponent.h"
#include "Utility/GCS_CombatFunctionLibrary.h"
UGCS_CombatSystemComponent* UGCS_CombatAbility::GetCombatSystemFromActorInfo() const
{
if (UGCS_CombatSystemComponent* CSS = UGCS_CombatSystemComponent::GetCombatSystemComponent(GetAvatarActorFromActorInfo()))
{
return CSS;
}
return nullptr;
}
UObject* UGCS_CombatAbility::GetCombatEntityFromActorInfo() const
{
return UGCS_CombatFunctionLibrary::GetCombatEntity(GetAvatarActorFromActorInfo());
}
TScriptInterface<IGCS_CombatEntityInterface> UGCS_CombatAbility::GetCombatEntityInterfaceFromActorInfo() const
{
return UGCS_CombatFunctionLibrary::GetCombatEntityInterface(GetAvatarActorFromActorInfo());
}

View File

@@ -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