Files
PHY/Plugins/GCS/Source/GenericCombatSystem/Private/GCS_CombatSystemComponent.cpp
2026-03-03 01:23:02 +08:00

319 lines
9.0 KiB
C++

// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GCS_CombatSystemComponent.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "TimerManager.h"
#include "GCS_LogChannels.h"
#include "Engine/World.h"
#include "GameFramework/Controller.h"
#include "Components/SkeletalMeshComponent.h"
#include "CombatFlow/GCS_AttackRequest.h"
#include "CombatFlow/GCS_CombatFlow.h"
#include "GameFramework/GameStateBase.h"
#include "Net/UnrealNetwork.h"
#include "Animation/AnimMontage.h"
#include "Animation/AnimInstance.h"
#include "Utility/GCS_CombatFunctionLibrary.h"
UGCS_CombatSystemComponent::UGCS_CombatSystemComponent() : AttackResultContainer(this, 10)
{
SetIsReplicatedByDefault(true);
bWantsInitializeComponent = true;
bReplicateUsingRegisteredSubObjectList = true;
}
void UGCS_CombatSystemComponent::InitializeComponent()
{
AttackResultContainer.SetOwningCombatSystem(this);
Super::InitializeComponent();
}
void UGCS_CombatSystemComponent::BeginPlay()
{
if (GetWorld()->IsGameWorld())
{
if (GetOwner()->HasAuthority() && CombatFlowClass)
{
UGCS_CombatFlow* LocalNewProperty = NewObject<UGCS_CombatFlow>(GetOwner(), CombatFlowClass);
LocalNewProperty->Initialize(GetOwner());
CombatFlow = LocalNewProperty;
AddReplicatedSubObject(CombatFlow);
}
UGGA_AbilitySystemGlobals::RegisterEventReceiver(this);
}
Super::BeginPlay();
}
void UGCS_CombatSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
UGGA_AbilitySystemGlobals::UnregisterEventReceiver(this);
if (GetOwner() && GetOwner()->HasAuthority())
{
if (CombatFlow)
{
RemoveReplicatedSubObject(CombatFlow);
}
}
}
void UGCS_CombatSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, CombatFlow);
DOREPLIFETIME(ThisClass, AttackResultContainer);
DOREPLIFETIME(ThisClass, ReplicatedMontageInfo);
FDoRepLifetimeParams Parameters;
Parameters.bIsPushBased = true;
Parameters.Condition = COND_SkipOwner;
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ComboStep, Parameters)
}
UGCS_CombatSystemComponent* UGCS_CombatSystemComponent::GetCombatSystemComponent(const AActor* Actor)
{
return Actor ? Actor->FindComponentByClass<UGCS_CombatSystemComponent>() : nullptr;
}
bool UGCS_CombatSystemComponent::FindCombatSystemComponent(const AActor* Actor, UGCS_CombatSystemComponent*& CombatComponent)
{
CombatComponent = Actor ? Actor->FindComponentByClass<UGCS_CombatSystemComponent>() : nullptr;
return IsValid(CombatComponent);
}
bool UGCS_CombatSystemComponent::FindTypedCombatSystemComponent(AActor* Actor, TSubclassOf<UGCS_CombatSystemComponent> DesiredClass, UGCS_CombatSystemComponent*& Component)
{
if (UGCS_CombatSystemComponent* Instance = GetCombatSystemComponent(Actor))
{
if (Instance->GetClass()->IsChildOf(DesiredClass))
{
Component = Instance;
return true;
}
}
return false;
}
UGCS_CombatFlow* UGCS_CombatSystemComponent::GetCombatFlow() const
{
return CombatFlow;
}
void UGCS_CombatSystemComponent::RegisterAttackResult(FGCS_AttackResult& Payload)
{
//TODO Should make this server only?
if (GetOwner() && GetOwner()->HasAuthority())
{
}
AttackResultContainer.AddEntry(Payload);
}
FGCS_AttackResult UGCS_CombatSystemComponent::GetLastProcessedAttackResult() const
{
return LastProcessedAttackResult;
}
void UGCS_CombatSystemComponent::SetLastProcessedAttackResult(const FGCS_AttackResult& Payload)
{
LastProcessedAttackResult = Payload;
}
void UGCS_CombatSystemComponent::PlayPredictableMontageForTarget(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request)
{
if (GetOwnerRole() >= ROLE_Authority)
{
TargetCSC->SetReplicatedMontage(Request);
}
if (GetOwnerRole() == ROLE_AutonomousProxy)
{
TargetCSC->PlayPredictedMontage(Request);
ServerPlayPredictableMontageForTarget(TargetCSC, Request);
}
}
void UGCS_CombatSystemComponent::ServerPlayPredictableMontageForTarget_Implementation(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request)
{
TargetCSC->SetReplicatedMontage(Request);
}
void UGCS_CombatSystemComponent::SetReplicatedMontage(const FGCS_PlayMontageRequest& Request)
{
TimerHandle.Invalidate();
ReplicatedMontageInfo.AnimMontage = Request.AnimMontage;
ReplicatedMontageInfo.PlayRate = Request.PlayRate;
ReplicatedMontageInfo.TriggeredTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
ReplicatedMontageInfo.StartSectionName = Request.StartSectionName;
GetWorld()->GetTimerManager().SetTimer(TimerHandle, [&]()
{
ReplicatedMontageInfo.AnimMontage = nullptr;
TimerHandle.Invalidate();
}, Request.AnimMontage->GetPlayLength() * Request.PlayRate, false);
OnRep_ReplicatedMontageInfo();
}
//server tell me to play montage.
void UGCS_CombatSystemComponent::OnRep_ReplicatedMontageInfo()
{
if (ReplicatedMontageInfo.AnimMontage == nullptr)
{
PredictedMontageInfo.AnimMontage = nullptr;
return;
}
if (USkeletalMeshComponent* MeshComponent = GetCharacterMeshComponent())
{
UAnimMontage* MontageToPlay = ReplicatedMontageInfo.AnimMontage;
float TimeDiff = GetWorld()->GetGameState()->GetServerWorldTimeSeconds() - ReplicatedMontageInfo.TriggeredTime;
float StartTime = FMath::Clamp(TimeDiff, 0, ReplicatedMontageInfo.AnimMontage->GetPlayLength() * ReplicatedMontageInfo.PlayRate);
//If local montage ahead of replicated montage
if (PredictedMontageInfo.AnimMontage != nullptr)
{
//And it's the same.
if (ReplicatedMontageInfo.AnimMontage == PredictedMontageInfo.AnimMontage)
{
PredictedMontageInfo.AnimMontage = nullptr;
return;
}
PredictedMontageInfo.AnimMontage = nullptr;
}
MeshComponent->GetAnimInstance()->Montage_Play(MontageToPlay, ReplicatedMontageInfo.PlayRate, EMontagePlayReturnType::MontageLength, StartTime);
}
}
void UGCS_CombatSystemComponent::PlayPredictedMontage(const FGCS_PlayMontageRequest& Request)
{
PredictedMontageInfo.AnimMontage = Request.AnimMontage;
PredictedMontageInfo.PlayRate = Request.PlayRate;
PredictedMontageInfo.TriggeredTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
PredictedMontageInfo.StartSectionName = Request.StartSectionName;
if (USkeletalMeshComponent* MeshComponent = GetCharacterMeshComponent())
{
float Duration = MeshComponent->GetAnimInstance()->Montage_Play(Request.AnimMontage, Request.PlayRate, EMontagePlayReturnType::MontageLength, Request.StartTimeSeconds);
if (Duration > 0)
{
// Start at a given Section.
if (Request.StartSectionName != NAME_None)
{
MeshComponent->GetAnimInstance()->Montage_JumpToSection(Request.StartSectionName, Request.AnimMontage);
}
}
}
}
USkeletalMeshComponent* UGCS_CombatSystemComponent::GetCharacterMeshComponent() const
{
static FName CharacterMeshTagName = "CharacterMesh";
return Cast<USkeletalMeshComponent>(GetOwner()->FindComponentByTag(USkeletalMeshComponent::StaticClass(), CharacterMeshTagName));
}
void UGCS_CombatSystemComponent::OnGlobalPreGameplayEffectSpecApply(FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent)
{
if (IsValid(CombatFlow))
{
FGameplayTagContainer DynamicTags;
CombatFlow->HandlePreGameplayEffectSpecApply(Spec, AbilitySystemComponent, DynamicTags);
if (!DynamicTags.IsEmpty())
{
Spec.AppendDynamicAssetTags(DynamicTags);
}
}
}
void UGCS_CombatSystemComponent::OnRep_CombatFlow()
{
CombatFlow->Initialize(GetOwner());
UE_LOG(LogGCS, Display, TEXT("Combat flow replicated for %s"), *GetOwner()->GetName());
}
int32 UGCS_CombatSystemComponent::GetComboStep() const
{
return ComboStep;
}
void UGCS_CombatSystemComponent::UpdateComboStep(int32 NewComboStep)
{
if (NewComboStep >= 0)
{
UpdateComboStep(NewComboStep, true);
}
}
void UGCS_CombatSystemComponent::ResetComboState()
{
if (ComboStep != 0)
{
UpdateComboStep(0,true);
}
}
void UGCS_CombatSystemComponent::UpdateComboStep(int32 NewComboStep, bool bSendRpc)
{
if (ComboStep == NewComboStep || GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy || ComboStep == NewComboStep)
{
return;
}
const auto PrevComboStep{ComboStep};
ComboStep = NewComboStep;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ComboStep, this)
OnComboStepChanged(PrevComboStep);
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientUpdateComboStep(ComboStep);
}
else
{
ServerUpdateComboStep(ComboStep);
}
}
}
void UGCS_CombatSystemComponent::OnReplicated_ComboStep(int32 PrevComboStep)
{
OnComboStepChanged(PrevComboStep);
}
void UGCS_CombatSystemComponent::ClientUpdateComboStep_Implementation(int32 NewComboStep)
{
UpdateComboStep(NewComboStep, false);
}
bool UGCS_CombatSystemComponent::ClientUpdateComboStep_Validate(int32 NewComboStep)
{
return true;
}
void UGCS_CombatSystemComponent::ServerUpdateComboStep_Implementation(int32 NewComboStep)
{
UpdateComboStep(NewComboStep, false);
}
bool UGCS_CombatSystemComponent::ServerUpdateComboStep_Validate(int32 NewComboStep)
{
return true;
}
void UGCS_CombatSystemComponent::OnComboStepChanged_Implementation(int32 PrevComboStep)
{
OnComboStepChangedEvent.Broadcast(PrevComboStep);
}