// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "CombatFlow/GCS_AttackResult.h" #include "GameplayEffect.h" #include "GCS_LogChannels.h" #include "GCS_CombatSystemComponent.h" #include "CombatFlow/GCS_CombatFlow.h" #include "Utility/GCS_CombatFunctionLibrary.h" void FGCS_AttackResult::PostReplicatedAdd(const struct FGCS_AttackResultContainer& InArray) { } FGCS_AttackResultContainer::FGCS_AttackResultContainer(): CombatFlow(nullptr), CombatSystemComponent(nullptr), MaxSize(5) { } FGCS_AttackResultContainer::FGCS_AttackResultContainer(UGCS_CombatFlow* InCombatFlow, int32 InMaxSize): CombatFlow(InCombatFlow), CombatSystemComponent(nullptr), MaxSize(InMaxSize) { } FGCS_AttackResultContainer::FGCS_AttackResultContainer(UGCS_CombatSystemComponent* InCombatSystemComponent, int32 InMaxSize): CombatFlow(nullptr), CombatSystemComponent(InCombatSystemComponent), MaxSize(InMaxSize) { } void FGCS_AttackResultContainer::AddEntry(FGCS_AttackResult& NewEntry) { if (Results.Num() >= 5) { Results.RemoveAtSwap(0); MarkArrayDirty(); } Results.Add(NewEntry); check(CombatSystemComponent != nullptr && CombatSystemComponent->GetOwner()); if (CombatSystemComponent->GetCombatFlow()) { CombatSystemComponent->GetCombatFlow()->HandleAttackResult(NewEntry); NewEntry.bConsumed = true; } else { UE_LOG(LogGCS, Error, TEXT("Missing Combat Flow on %s's combat system component."), *CombatSystemComponent->GetOwner()->GetName()); } MarkItemDirty(NewEntry); } void FGCS_AttackResultContainer::PostReplicatedAdd(const TArrayView AddedIndices, int32 FinalSize) { for (int32 Index : AddedIndices) { FGCS_AttackResult& Entry = Results[Index]; FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(Results[Index].EffectContextHandle); if (CombatPayload != nullptr && CombatPayload->PredictionKey.IsLocalClientKey()) { checkf(!CombatPayload->bIsPredictingContext, TEXT("PredictingContext never should hit this!")) // PredictionKey will only be valid on the client that predicted it. So if this has a valid PredictionKey, we can assume we already predicted it and shouldn't handle attack results. if (HasPredictedResultWithPredictedKey(CombatPayload->PredictionKey)) { GCS_LOG(Verbose, "Found already predicted attack result!") Entry.bWasPredicated = true; } } Entry.bWasReplicated = true; CombatSystemComponent->GetCombatFlow()->HandleAttackResult(Entry); Entry.bConsumed = true; } } void FGCS_AttackResultContainer::PostReplicatedChange(const TArrayView& ChangedIndices, int32 FinalSize) { for (int32 Index : ChangedIndices) { if (!Results[Index].bConsumed) { CombatSystemComponent->GetCombatFlow()->HandleAttackResult(Results[Index]); Results[Index].bConsumed = true; } } } bool FGCS_AttackResultContainer::HasPredictedResultWithPredictedKey(FPredictionKey PredictionKey) const { for (const FGCS_AttackResult& Result : Results) { if (Result.bConsumed) { continue; } if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(Result.EffectContextHandle)) { if (CombatPayload->PredictionKey.IsValidKey() && CombatPayload->PredictionKey == PredictionKey && CombatPayload->PredictionKey.WasReceived() == false) { return true; } } } return false; }