// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraWeaponStateComponent.h" #include "Teams/LyraTeamSubsystem.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" #include "Abilities/GameplayAbilityTargetTypes.h" #include "AbilitySystemComponent.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/Pawn.h" #include "Equipment/LyraEquipmentManagerComponent.h" #include "Weapons/LyraRangedWeaponInstance.h" #include "NativeGameplayTags.h" #include "Physics/PhysicalMaterialWithTags.h" UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Gameplay_Zone, "Gameplay.Zone"); ULyraWeaponStateComponent::ULyraWeaponStateComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SetIsReplicatedByDefault(true); PrimaryComponentTick.bStartWithTickEnabled = true; PrimaryComponentTick.bCanEverTick = true; } void ULyraWeaponStateComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (APawn* Pawn = GetPawn()) { if (ULyraEquipmentManagerComponent* EquipmentManager = Pawn->FindComponentByClass()) { if (ULyraRangedWeaponInstance* CurrentWeapon = Cast(EquipmentManager->GetFirstInstanceOfType(ULyraRangedWeaponInstance::StaticClass()))) { CurrentWeapon->Tick(DeltaTime); } } } } bool ULyraWeaponStateComponent::ShouldShowHitAsSuccess(const FHitResult& Hit) const { AActor* HitActor = Hit.GetActor(); //@TODO: Don't treat a hit that dealt no damage (due to invulnerability or similar) as a success UWorld* World = GetWorld(); if (ULyraTeamSubsystem* TeamSubsystem = UWorld::GetSubsystem(GetWorld())) { return TeamSubsystem->CanCauseDamage(GetController(), Hit.GetActor()); } return false; } bool ULyraWeaponStateComponent::ShouldUpdateDamageInstigatedTime(const FGameplayEffectContextHandle& EffectContext) const { //@TODO: Implement me, for the purposes of this component we really only care about damage caused by a weapon // or projectile fired from a weapon, and should filter to that // (or perhaps see if the causer is also the source of our active reticle config) return EffectContext.GetEffectCauser() != nullptr; } void ULyraWeaponStateComponent::ClientConfirmTargetData_Implementation(uint16 UniqueId, bool bSuccess, const TArray& HitReplaces) { for (int i = 0; i < UnconfirmedServerSideHitMarkers.Num(); i++) { FLyraServerSideHitMarkerBatch& Batch = UnconfirmedServerSideHitMarkers[i]; if (Batch.UniqueId == UniqueId) { if (bSuccess && (HitReplaces.Num() != Batch.Markers.Num())) { UWorld* World = GetWorld(); bool bFoundShowAsSuccessHit = false; int32 HitLocationIndex = 0; for (const FLyraScreenSpaceHitLocation& Entry : Batch.Markers) { if (!HitReplaces.Contains(HitLocationIndex) && Entry.bShowAsSuccess) { // Only need to do this once if (!bFoundShowAsSuccessHit) { ActuallyUpdateDamageInstigatedTime(); } bFoundShowAsSuccessHit = true; LastWeaponDamageScreenLocations.Add(Entry); } ++HitLocationIndex; } } UnconfirmedServerSideHitMarkers.RemoveAt(i); break; } } } void ULyraWeaponStateComponent::AddUnconfirmedServerSideHitMarkers(const FGameplayAbilityTargetDataHandle& InTargetData, const TArray& FoundHits) { FLyraServerSideHitMarkerBatch& NewUnconfirmedHitMarker = UnconfirmedServerSideHitMarkers.Emplace_GetRef(InTargetData.UniqueId); if (APlayerController* OwnerPC = GetController()) { for (const FHitResult& Hit : FoundHits) { FVector2D HitScreenLocation; if (UGameplayStatics::ProjectWorldToScreen(OwnerPC, Hit.Location, /*out*/ HitScreenLocation, /*bPlayerViewportRelative=*/ false)) { FLyraScreenSpaceHitLocation& Entry = NewUnconfirmedHitMarker.Markers.AddDefaulted_GetRef(); Entry.Location = HitScreenLocation; Entry.bShowAsSuccess = ShouldShowHitAsSuccess(Hit); // Determine the hit zone FGameplayTag HitZone; if (const UPhysicalMaterialWithTags* PhysMatWithTags = Cast(Hit.PhysMaterial.Get())) { for (const FGameplayTag MaterialTag : PhysMatWithTags->Tags) { if (MaterialTag.MatchesTag(TAG_Gameplay_Zone)) { Entry.HitZone = MaterialTag; break; } } } } } } } void ULyraWeaponStateComponent::UpdateDamageInstigatedTime(const FGameplayEffectContextHandle& EffectContext) { if (ShouldUpdateDamageInstigatedTime(EffectContext)) { ActuallyUpdateDamageInstigatedTime(); } } void ULyraWeaponStateComponent::ActuallyUpdateDamageInstigatedTime() { // If our LastWeaponDamageInstigatedTime was not very recent, clear our LastWeaponDamageScreenLocations array UWorld* World = GetWorld(); if (World->GetTimeSeconds() - LastWeaponDamageInstigatedTime > 0.1) { LastWeaponDamageScreenLocations.Reset(); } LastWeaponDamageInstigatedTime = World->GetTimeSeconds(); } double ULyraWeaponStateComponent::GetTimeSinceLastHitNotification() const { UWorld* World = GetWorld(); return World->TimeSince(LastWeaponDamageInstigatedTime); }