// 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(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& 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() : nullptr; } bool UGCS_CombatSystemComponent::FindCombatSystemComponent(const AActor* Actor, UGCS_CombatSystemComponent*& CombatComponent) { CombatComponent = Actor ? Actor->FindComponentByClass() : nullptr; return IsValid(CombatComponent); } bool UGCS_CombatSystemComponent::FindTypedCombatSystemComponent(AActor* Actor, TSubclassOf 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(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); }