// Fill out your copyright notice in the Description page of Project Settings. #include "Combat/CombatComponent.h" #include "Character/ShooterCharacter.h" #include "Data/WeaponData.h" #include "Net/UnrealNetwork.h" #include "Weapon/Weapon.h" #include "TimerManager.h" #include "FPSTemplate/FPSTemplate.h" #include "Kismet/GameplayStatics.h" UCombatComponent::UCombatComponent() { PrimaryComponentTick.bCanEverTick = true; CarriedAmmo = 0; Local_WeaponIndex = 0; bTriggerPressed = false; bAiming = false; TraceLength = 20'000.f; } void UCombatComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION(UCombatComponent, CarriedAmmo, COND_OwnerOnly); DOREPLIFETIME(UCombatComponent, Inventory); DOREPLIFETIME(UCombatComponent, CurrentWeapon); DOREPLIFETIME_CONDITION(UCombatComponent, bAiming, COND_SkipOwner); } void UCombatComponent::AddAmmo(FGameplayTag WeaponType, int32 AmmoAmount) { if (GetOwner()->HasAuthority() && IsValid(CurrentWeapon)) { int32 NewAmmo = CarriedAmmoMap.FindChecked(WeaponType) + AmmoAmount; CarriedAmmoMap[WeaponType] = NewAmmo; if (CurrentWeapon->WeaponType.MatchesTagExact(WeaponType)) { CarriedAmmo = NewAmmo; if (CurrentWeapon->Ammo == 0 && NewAmmo > 0) { Server_ReloadWeapon(true); } } OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity); OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo); } } void UCombatComponent::InitializeWeaponWidgets() const { if (IsValid(CurrentWeapon)) { OnReticleChanged.Broadcast(CurrentWeapon->GetReticleDynamicMaterialInstance(), CurrentWeapon->ReticleParams, Local_PlayerHitResult.bHitPlayer); OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity); OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo); } } void UCombatComponent::SpawnDefaultInventory() { AActor* OwningActor = GetOwner(); if (!IsValid(OwningActor)) return; if (OwningActor->GetLocalRole() < ROLE_Authority) { return; } for (TSubclassOf& WeaponClass : DefaultInventoryClasses) { FActorSpawnParameters SpawnInfo; SpawnInfo.Instigator = Cast(OwningActor); SpawnInfo.Owner = OwningActor; SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AWeapon* NewWeapon = GetWorld()->SpawnActor(WeaponClass, SpawnInfo); CarriedAmmoMap.Add(NewWeapon->WeaponType, NewWeapon->StartingCarriedAmmo); AddWeapon(NewWeapon); } // equip first weapon in inventory if (Inventory.Num() > 0) { EquipWeapon(Inventory[0]); CarriedAmmo = CarriedAmmoMap.FindChecked(Inventory[0]->WeaponType); } } void UCombatComponent::DestroyInventory() { for (AWeapon* Weapon : Inventory) { if (IsValid(Weapon)) { Weapon->Destroy(); } } } void UCombatComponent::OnRep_CurrentWeapon(AWeapon* LastWeapon) { SetCurrentWeapon(CurrentWeapon, LastWeapon); check(GetOwner()); check(GetOwner()->Implements()); IPlayerInterface::Execute_WeaponReplicated(GetOwner()); } void UCombatComponent::ServerEquipWeapon_Implementation(AWeapon* NewWeapon) { EquipWeapon(NewWeapon); } void UCombatComponent::BeginPlay() { Super::BeginPlay(); } void UCombatComponent::OnRep_CarriedAmmo() { if (IsValid(CurrentWeapon)) { OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity); OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo); } } void UCombatComponent::SetCurrentWeapon(AWeapon* NewWeapon, AWeapon* LastWeapon) { AWeapon* LocalLastWeapon = nullptr; if (IsValid(LastWeapon)) { LocalLastWeapon = LastWeapon; } else if (NewWeapon != CurrentWeapon) { LocalLastWeapon = CurrentWeapon; } // unequip previous if (IsValid(LocalLastWeapon)) { LocalLastWeapon->OnUnEquip(); } CurrentWeapon = NewWeapon; APawn* OwningPawn = Cast(GetOwner()); if (IsValid(OwningPawn) && OwningPawn->HasAuthority() && IsValid(CurrentWeapon)) { CarriedAmmo = CarriedAmmoMap.FindChecked(CurrentWeapon->WeaponType); } // equip new one if (IsValid(NewWeapon)) { NewWeapon->SetOwningPawn(Cast(GetOwner())); NewWeapon->OnEquip(LastWeapon); } if (IsValid(CurrentWeapon) && CurrentWeapon->Ammo == 0 && CarriedAmmo > 0 && IsValid(OwningPawn) && OwningPawn->IsLocallyControlled()) { Local_ReloadWeapon(); Server_ReloadWeapon(); } } void UCombatComponent::AddWeapon(AWeapon* Weapon) { if (IsValid(Weapon) && IsValid(GetOwner()) && GetOwner()->GetLocalRole() == ROLE_Authority) { Weapon->OnEnterInventory(Cast(GetOwner())); Inventory.AddUnique(Weapon); } } void UCombatComponent::EquipWeapon(AWeapon* Weapon) { if (!IsValid(Weapon) || !IsValid(GetOwner())) return; if (GetOwner()->GetLocalRole() == ROLE_Authority) { SetCurrentWeapon(Weapon, CurrentWeapon); } else { ServerEquipWeapon(Weapon); } } void UCombatComponent::Initiate_CycleWeapon() { if (!IsValid(CurrentWeapon)) return; if (CurrentWeapon->GetWeaponState() == EWeaponState::Equipping) return; if (!IsValid(GetOwner())) return; if (GetOwner()->Implements() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) return; AdvanceWeaponIndex(); Local_CycleWeapon(Local_WeaponIndex); Server_CycleWeapon(Local_WeaponIndex); } void UCombatComponent::Notify_CycleWeapon() { if (!IsValid(CurrentWeapon)) return; CurrentWeapon->SetWeaponState(EWeaponState::Idle); AWeapon* NextWeapon = Inventory[Local_WeaponIndex]; if (IsValid(NextWeapon)) { EquipWeapon(NextWeapon); } CurrentWeapon->SetWeaponState(EWeaponState::Equipping); } void UCombatComponent::Local_CycleWeapon(const int32 WeaponIndex) { if (!IsValid(GetOwner()) || !GetOwner()->Implements()) return; const AWeapon* NextWeapon = Inventory[WeaponIndex]; if (!IsValid(NextWeapon) || !IsValid(WeaponData)) return; const FMontageData& FirstPersonMontages = WeaponData->FirstPersonMontages.FindChecked(NextWeapon->WeaponType); USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true); if (IsValid(Mesh1P)) { Mesh1P->GetAnimInstance()->Montage_Play(FirstPersonMontages.EquipMontage); Mesh1P->GetAnimInstance()->OnMontageBlendingOut.AddDynamic(this, &UCombatComponent::BlendOut_CycleWeapon); } const FMontageData& ThirdPersonMontages = WeaponData->ThirdPersonMontages.FindChecked(NextWeapon->WeaponType); USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false); if (IsValid(Mesh3P)) { Mesh3P->GetAnimInstance()->Montage_Play(ThirdPersonMontages.EquipMontage); } if (IsValid(CurrentWeapon)) { CurrentWeapon->SetWeaponState(EWeaponState::Equipping); } } void UCombatComponent::Multicast_CycleWeapon_Implementation(const int32 WeaponIndex) { if (!IsValid(Cast(GetOwner())) || Cast(GetOwner())->IsLocallyControlled()) return; Local_WeaponIndex = WeaponIndex; Local_CycleWeapon(WeaponIndex); } void UCombatComponent::Server_CycleWeapon_Implementation(const int32 WeaponIndex) { Local_WeaponIndex = WeaponIndex; Multicast_CycleWeapon(WeaponIndex); } int32 UCombatComponent::AdvanceWeaponIndex() { if (Inventory.Num() >= 2) { Local_WeaponIndex = (Local_WeaponIndex + 1) % Inventory.Num(); } return Local_WeaponIndex; } void UCombatComponent::BlendOut_CycleWeapon(UAnimMontage* Montage, bool bInterrupted) { UAnimInstance* AnimInstance = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true)->GetAnimInstance(); if (IsValid(AnimInstance) && AnimInstance->OnMontageBlendingOut.IsAlreadyBound(this, &UCombatComponent::BlendOut_CycleWeapon)) { AnimInstance->OnMontageBlendingOut.RemoveDynamic(this, &UCombatComponent::BlendOut_CycleWeapon); } if (!IsValid(CurrentWeapon)) return; CurrentWeapon->SetWeaponState(EWeaponState::Idle); OnReticleChanged.Broadcast(CurrentWeapon->GetReticleDynamicMaterialInstance(), CurrentWeapon->ReticleParams, Local_PlayerHitResult.bHitPlayer); OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity); OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo); if (bTriggerPressed && CurrentWeapon->FireType == EFireType::Auto && CurrentWeapon->Ammo > 0) { Local_FireWeapon(); } } void UCombatComponent::Initiate_FireWeapon_Pressed() { bTriggerPressed = true; if (!IsValid(GetOwner())) return; if (GetOwner()->Implements() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) { bTriggerPressed = false; return; } if (!IsValid(CurrentWeapon)) return; if (CurrentWeapon->GetWeaponState() == EWeaponState::Idle && CurrentWeapon->Ammo > 0) { Local_FireWeapon(); } } void UCombatComponent::Initiate_FireWeapon_Released() { bTriggerPressed = false; } void UCombatComponent::Local_FireWeapon() { if (!IsValid(WeaponData) || !IsValid(CurrentWeapon)) return; UAnimMontage* Montage1P = WeaponData->FirstPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage; USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true); if (IsValid(Montage1P) && IsValid(Mesh1P)) { Mesh1P->GetAnimInstance()->Montage_Play(Montage1P); } UAnimMontage* Montage3P = WeaponData->ThirdPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage; USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false); if (IsValid(Montage3P) && IsValid(Mesh3P)) { Mesh3P->GetAnimInstance()->Montage_Play(Montage3P); } APawn* OwningPawn = Cast(GetOwner()); if (IsValid(OwningPawn) && OwningPawn->IsLocallyControlled()) { GetWorld()->GetTimerManager().SetTimer(FireTimer, this, &UCombatComponent::FireTimerFinished, CurrentWeapon->FireTime); FHitResult Hit; FVector TraceStart = HitScanTrace(5.f, Hit); // Tell the weapon the impact point, normal, and physical material EPhysicalSurface SurfaceType = Hit.PhysMaterial.IsValid(false) ? Hit.PhysMaterial->SurfaceType.GetValue() : EPhysicalSurface::SurfaceType1; CurrentWeapon->Local_Fire(Hit.ImpactPoint, Hit.ImpactNormal, SurfaceType, true); // Send the server the hit info. const bool bHitPlayer = IsValid(Hit.GetActor()) ? Hit.GetActor()->Implements() : false; const bool bHeadShot = Hit.BoneName == "head"; OnRoundFired.Broadcast(CurrentWeapon->Ammo, CurrentWeapon->MagCapacity, CarriedAmmo); if (GetNetMode() == NM_Standalone) return; Server_FireWeapon(TraceStart, Hit, bHitPlayer, bHeadShot); } } void UCombatComponent::Server_FireWeapon_Implementation(const FVector_NetQuantize& TraceStart, const FHitResult& Impact, bool bScoredHit, bool bHeadShot) { // Do your server-side rewind validation here... if (!IsValid(CurrentWeapon) || !IsValid(GetOwner())) return; float Damage = bHeadShot ? CurrentWeapon->HeadShotDamage : CurrentWeapon->Damage; bool bLethal = false; bool bHit = false; if (IsValid(Impact.GetActor()) && Impact.GetActor()->Implements()) { bLethal = IPlayerInterface::Execute_DoDamage(Impact.GetActor(), Damage, GetOwner()); bHit = true; } else { bHit = false; } OnRoundReported.Broadcast(GetOwner(), Impact.GetActor(), bHit, bHeadShot, bLethal); if (GetNetMode() != NM_ListenServer || !Cast(GetOwner())->IsLocallyControlled()) { // We still need to update ammo server-side for non-hosting player-controlled proxies on a listen server CurrentWeapon->Auth_Fire(); } Multicast_FireWeapon(Impact, CurrentWeapon->Ammo); } void UCombatComponent::Multicast_FireWeapon_Implementation(const FHitResult& Impact, int32 AuthAmmo) { if (!IsValid(CurrentWeapon) || !IsValid(GetOwner())) return; if (Cast(GetOwner())->IsLocallyControlled()) { CurrentWeapon->Rep_Fire(AuthAmmo); } else { EPhysicalSurface SurfaceType = Impact.PhysMaterial.IsValid(false) ? Impact.PhysMaterial->SurfaceType.GetValue() : SurfaceType1; if (IsValid(CurrentWeapon)) { CurrentWeapon->Local_Fire(Impact.ImpactPoint, Impact.ImpactNormal, SurfaceType, false); if (IsValid(WeaponData)) { UAnimMontage* Montage1P = WeaponData->FirstPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage; USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true); if (IsValid(Mesh1P) && IsValid(Montage1P)) { Mesh1P->GetAnimInstance()->Montage_Play(Montage1P); } UAnimMontage* Montage3P = WeaponData->ThirdPersonMontages.FindChecked(CurrentWeapon->WeaponType).FireMontage; USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false); if (IsValid(Mesh3P) && IsValid(Montage3P)) { Mesh3P->GetAnimInstance()->Montage_Play(Montage3P); } } } } } void UCombatComponent::FireTimerFinished() { if (!IsValid(GetOwner())) return; if (GetOwner()->Implements() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) { bTriggerPressed = false; return; } if (!IsValid(CurrentWeapon)) return; if (CurrentWeapon->Ammo == 0 && CarriedAmmo > 0 && Cast(GetOwner())->IsLocallyControlled()) { Local_ReloadWeapon(); Server_ReloadWeapon(); return; } if (CurrentWeapon->FireType == EFireType::Auto && bTriggerPressed && CurrentWeapon->Ammo > 0) { Local_FireWeapon(); return; } CurrentWeapon->SetWeaponState(EWeaponState::Idle); } void UCombatComponent::Initiate_ReloadWeapon() { if (!IsValid(CurrentWeapon)) return; if (CurrentWeapon->GetWeaponState() == EWeaponState::Equipping || CurrentWeapon->GetWeaponState() == EWeaponState::Reloading) return; if (CurrentWeapon->Ammo == CurrentWeapon->MagCapacity || CarriedAmmo == 0) return; if (!IsValid(GetOwner())) return; if (GetOwner()->Implements() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) return; Local_ReloadWeapon(); Server_ReloadWeapon(); } void UCombatComponent::Local_ReloadWeapon() { if (!IsValid(CurrentWeapon) || !IsValid(GetOwner())) return; UAnimMontage* Montage1P = WeaponData->FirstPersonMontages.FindChecked(CurrentWeapon->WeaponType).ReloadMontage; USkeletalMeshComponent* Mesh1P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), true); if (IsValid(Montage1P) && IsValid(Mesh1P)) { Mesh1P->GetAnimInstance()->Montage_Play(Montage1P); } UAnimMontage* Montage3P = WeaponData->ThirdPersonMontages.FindChecked(CurrentWeapon->WeaponType).ReloadMontage; USkeletalMeshComponent* Mesh3P = IPlayerInterface::Execute_GetSpecifcPawnMesh(GetOwner(), false); if (IsValid(Montage3P) && IsValid(Mesh3P)) { Mesh3P->GetAnimInstance()->Montage_Play(Montage3P); } UAnimMontage* WeaponMontage = WeaponData->WeaponMontages.FindChecked(CurrentWeapon->WeaponType).ReloadMontage; if (IsValid(CurrentWeapon->GetMesh1P()) && IsValid(CurrentWeapon->GetMesh3P())) { CurrentWeapon->GetMesh1P()->GetAnimInstance()->Montage_Play(WeaponMontage); CurrentWeapon->GetMesh3P()->GetAnimInstance()->Montage_Play(WeaponMontage); } CurrentWeapon->SetWeaponState(EWeaponState::Reloading); } void UCombatComponent::Server_ReloadWeapon_Implementation(bool bLocalOwnerReload) { Multicast_ReloadWeapon(CurrentWeapon->Ammo, CarriedAmmo, bLocalOwnerReload); } void UCombatComponent::Multicast_ReloadWeapon_Implementation(int32 NewWeaponAmmo, int32 NewCarriedAmmo, bool bLocalOwnerReload) { Local_ReloadWeapon(); } void UCombatComponent::Client_ReloadWeapon_Implementation(int32 NewWeaponAmmo, int32 NewCarriedAmmo) { if (!IsValid(GetOwner()) || !IsValid(CurrentWeapon)) return; if (Cast(GetOwner())->IsLocallyControlled()) { CurrentWeapon->Ammo = NewWeaponAmmo; CarriedAmmo = NewCarriedAmmo; OnAmmoCounterChanged.Broadcast(CurrentWeapon->GetAmmoCounterDynamicMaterialInstance(), CurrentWeapon->Ammo, CurrentWeapon->MagCapacity); OnCarriedAmmoChanged.Broadcast(CurrentWeapon->GetWeaponIconDynamicMaterialInstance(), CarriedAmmo, CurrentWeapon->Ammo); } } void UCombatComponent::Notify_ReloadWeapon() { if (!IsValid(CurrentWeapon)) return; if (GetNetMode() == NM_ListenServer || GetNetMode() == NM_DedicatedServer || GetNetMode() == NM_Standalone) { int32 EmptySpace = CurrentWeapon->MagCapacity - CurrentWeapon->Ammo; int32 AmountToRefill = FMath::Min(EmptySpace, CarriedAmmo); CurrentWeapon->Ammo += AmountToRefill; CarriedAmmoMap[CurrentWeapon->WeaponType] = CarriedAmmoMap[CurrentWeapon->WeaponType] - AmountToRefill; CarriedAmmo = CarriedAmmoMap[CurrentWeapon->WeaponType]; Client_ReloadWeapon(CurrentWeapon->Ammo, CarriedAmmo); } CurrentWeapon->SetWeaponState(EWeaponState::Idle); if (bTriggerPressed && CurrentWeapon->FireType == EFireType::Auto && CurrentWeapon->Ammo > 0) { Local_FireWeapon(); } } void UCombatComponent::Initiate_AimPressed() { if (!IsValid(GetOwner())) return; if (GetOwner()->Implements() && IPlayerInterface::Execute_IsDeadOrDying(GetOwner())) return; Local_Aim(true); Server_Aim(true); } void UCombatComponent::Server_Aim_Implementation(bool bPressed) { Local_Aim(bPressed); } void UCombatComponent::Initiate_AimReleased() { Local_Aim(false); Server_Aim(false); } void UCombatComponent::Local_Aim(bool bPressed) { bAiming = bPressed; OnAimingStatusChanged.Broadcast(bAiming); OnAim(bPressed); } void UCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); APawn* OwningPawn = Cast(GetOwner()); if (!IsValid(OwningPawn) || !OwningPawn->IsLocallyControlled()) return; if (APlayerController* PC = Cast(OwningPawn->GetController()); IsValid(PC)) { FVector2D ViewportSize; if (GEngine && GEngine->GameViewport) { GEngine->GameViewport->GetViewportSize(ViewportSize); } FVector2D ReticleLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f); FVector ReticleWorldLocation; FVector ReticleWorlDirection; UGameplayStatics::DeprojectScreenToWorld(PC, ReticleLocation, ReticleWorldLocation, ReticleWorlDirection); Local_PlayerHitResult.Start = ReticleWorldLocation; Local_PlayerHitResult.End = Local_PlayerHitResult.Start + ReticleWorlDirection * TraceLength; Local_PlayerHitResult.bHitPlayerLastFrame = Local_PlayerHitResult.bHitPlayer; FHitResult TraceHit; FCollisionQueryParams QueryParams; QueryParams.AddIgnoredActor(GetOwner()); for (AWeapon* Weapon : Inventory) { QueryParams.AddIgnoredActor(Weapon); } FCollisionResponseParams ResponseParams; ResponseParams.CollisionResponse.SetAllChannels(ECR_Ignore); ResponseParams.CollisionResponse.SetResponse(ECC_WorldStatic, ECR_Block); ResponseParams.CollisionResponse.SetResponse(ECC_WorldDynamic, ECR_Block); ResponseParams.CollisionResponse.SetResponse(ECC_PhysicsBody, ECR_Block); ResponseParams.CollisionResponse.SetResponse(ECC_Pawn, ECR_Block); GetWorld()->LineTraceSingleByChannel(TraceHit, Local_PlayerHitResult.Start, Local_PlayerHitResult.End, ECC_Weapon, QueryParams, ResponseParams); Local_PlayerHitResult.End = TraceHit.bBlockingHit ? TraceHit.ImpactPoint : Local_PlayerHitResult.End; if (IsValid(TraceHit.GetActor()) && TraceHit.GetActor()->Implements()) { Local_PlayerHitResult.bHitPlayer = true; } else { Local_PlayerHitResult.bHitPlayer = false; } Local_PlayerHitResult.bHeadShot = TraceHit.bBlockingHit && TraceHit.BoneName == "head"; if (Local_PlayerHitResult.bHitPlayer != Local_PlayerHitResult.bHitPlayerLastFrame) { OnTargetingPlayerStatusChanged.Broadcast(Local_PlayerHitResult.bHitPlayer); } } } FVector UCombatComponent::HitScanTrace(float SweepRadius, FHitResult& OutHit) { FVector Start = GetOwner()->GetActorLocation(); FCollisionQueryParams QueryParams; QueryParams.bReturnPhysicalMaterial = true; QueryParams.AddIgnoredActor(GetOwner()); for (AWeapon* Weapon : Inventory) { QueryParams.AddIgnoredActor(Weapon); } FCollisionResponseParams ResponseParams; ResponseParams.CollisionResponse.SetAllChannels(ECR_Ignore); ResponseParams.CollisionResponse.SetResponse(ECC_Pawn, ECR_Block); ResponseParams.CollisionResponse.SetResponse(ECC_WorldStatic, ECR_Block); ResponseParams.CollisionResponse.SetResponse(ECC_WorldDynamic, ECR_Block); ResponseParams.CollisionResponse.SetResponse(ECC_PhysicsBody, ECR_Block); FVector2D ViewportSize; if (GEngine && GEngine->GameViewport) { GEngine->GameViewport->GetViewportSize(ViewportSize); } if (APlayerController* PC = Cast(Cast(GetOwner())->GetController()); IsValid(PC)) { FVector2D ReticleLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f); FVector ReticleWorldLocation; FVector ReticleWorlDirection; UGameplayStatics::DeprojectScreenToWorld(PC, ReticleLocation, ReticleWorldLocation, ReticleWorlDirection); Start = ReticleWorldLocation; FVector End = Start + ReticleWorlDirection * TraceLength; const bool bHit = GetWorld()->SweepSingleByChannel(OutHit, Start, End, FQuat::Identity, ECC_Weapon, FCollisionShape::MakeSphere(SweepRadius), QueryParams, ResponseParams); if (!bHit) { OutHit.ImpactPoint = End; OutHit.ImpactNormal = (Start - End).GetSafeNormal(); } } return Start; }