319 lines
9.0 KiB
C++
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);
|
|
}
|