void AFournoidWeapon::FireBullet() { if ( BulletClass ) { if ( MyPawn ) { const auto SpawnRotation = MyPawn->GetControlRotation(); const auto SpawnLocation = GetMuzzleLocation(); const auto World = GetWorld(); if ( World ) { if ( Role == ROLE_Authority ) { if ( !IsClipEmpty() ) { auto SpawnedBullet = World->SpawnActor<AFournoidBullet>(BulletClass, SpawnLocation, SpawnRotation); SpawnedBullet->Instigator = Instigator; PlayShootingFX(); CurrentClipSize -= 1; GetWorldTimerManager().SetTimer(TimerHandle_HandleFireBullet, this, &AFournoidWeapon::FireBullet, FiringRate, false); } else { StartReloading(); } } } } } }
void ASWeaponInstant::FireWeapon() { const FVector AimDir = GetAdjustedAim(); const FVector CameraPos = GetCameraDamageStartLocation(AimDir); const FVector EndPos = CameraPos + (AimDir * WeaponRange); /* Check for impact by tracing from the camera position */ FHitResult Impact = WeaponTrace(CameraPos, EndPos); const FVector MuzzleOrigin = GetMuzzleLocation(); FVector AdjustedAimDir = AimDir; if (Impact.bBlockingHit) { /* Adjust the shoot direction to hit at the crosshair. */ AdjustedAimDir = (Impact.ImpactPoint - MuzzleOrigin).GetSafeNormal(); /* Re-trace with the new aim direction coming out of the weapon muzzle */ Impact = WeaponTrace(MuzzleOrigin, MuzzleOrigin + (AdjustedAimDir * WeaponRange)); } else { /* Use the maximum distance as the adjust direction */ Impact.ImpactPoint = FVector_NetQuantize(EndPos); } ProcessInstantHit(Impact, MuzzleOrigin, AdjustedAimDir); }
void ASWeaponInstant::ServerNotifyHit_Implementation(const FHitResult Impact, FVector_NetQuantizeNormal ShootDir) { // If we have an instigator, calculate the dot between the view and the shot if (Instigator && (Impact.GetActor() || Impact.bBlockingHit)) { const FVector Origin = GetMuzzleLocation(); const FVector ViewDir = (Impact.Location - Origin).GetSafeNormal(); const float ViewDotHitDir = FVector::DotProduct(Instigator->GetViewRotation().Vector(), ViewDir); if (ViewDotHitDir > AllowedViewDotHitDir) { // TODO: Check for weapon state if (Impact.GetActor() == nullptr) { if (Impact.bBlockingHit) { ProcessInstantHitConfirmed(Impact, Origin, ShootDir); } } // Assume it told the truth about static things because we don't move and the hit // usually doesn't have significant gameplay implications else if (Impact.GetActor()->IsRootComponentStatic() || Impact.GetActor()->IsRootComponentStationary()) { ProcessInstantHitConfirmed(Impact, Origin, ShootDir); } else { const FBox HitBox = Impact.GetActor()->GetComponentsBoundingBox(); FVector BoxExtent = 0.5 * (HitBox.Max - HitBox.Min); BoxExtent *= ClientSideHitLeeway; BoxExtent.X = FMath::Max(20.0f, BoxExtent.X); BoxExtent.Y = FMath::Max(20.0f, BoxExtent.Y); BoxExtent.Z = FMath::Max(20.0f, BoxExtent.Z); const FVector BoxCenter = (HitBox.Min + HitBox.Max) * 0.5; // If we are within client tolerance if (FMath::Abs(Impact.Location.Z - BoxCenter.Z) < BoxExtent.Z && FMath::Abs(Impact.Location.X - BoxCenter.X) < BoxExtent.X && FMath::Abs(Impact.Location.Y - BoxCenter.Y) < BoxExtent.Y) { ProcessInstantHitConfirmed(Impact, Origin, ShootDir); } } } } // TODO: UE_LOG on failures & rejection }
void AAmethystWeapon_Projectile::FireWeapon() { FVector ShootDir = GetAdjustedAim(); FVector Origin = GetMuzzleLocation(); // trace from camera to check what's under crosshair const float ProjectileAdjustRange = 10000.0f; const FVector StartTrace = GetCameraDamageStartLocation(ShootDir); const FVector EndTrace = StartTrace + ShootDir * ProjectileAdjustRange; FHitResult Impact = WeaponTrace(StartTrace, EndTrace); // and adjust directions to hit that actor if (Impact.bBlockingHit) { const FVector AdjustedDir = (Impact.ImpactPoint - Origin).SafeNormal(); bool bWeaponPenetration = false; const float DirectionDot = FVector::DotProduct(AdjustedDir, ShootDir); if (DirectionDot < 0.0f) { // shooting backwards = weapon is penetrating bWeaponPenetration = true; } else if (DirectionDot < 0.5f) { // check for weapon penetration if angle difference is big enough // raycast along weapon mesh to check if there's blocking hit FVector MuzzleStartTrace = Origin - GetMuzzleDirection() * 150.0f; FVector MuzzleEndTrace = Origin; FHitResult MuzzleImpact = WeaponTrace(MuzzleStartTrace, MuzzleEndTrace); if (MuzzleImpact.bBlockingHit) { bWeaponPenetration = true; } } if (bWeaponPenetration) { // spawn at crosshair position Origin = Impact.ImpactPoint - ShootDir * 10.0f; } else { // adjust direction to hit ShootDir = AdjustedDir; } } ServerFireProjectile(Origin, ShootDir); }
void ASWeaponInstant::ServerNotifyMiss_Implementation(FVector_NetQuantizeNormal ShootDir) { const FVector Origin = GetMuzzleLocation(); const FVector EndTrace = Origin + (ShootDir * WeaponRange); // Play on remote clients HitImpactNotify = EndTrace; if (GetNetMode() != NM_DedicatedServer) { SpawnTrailEffects(EndTrace); } }
void AShooterWeapon_Instant::SpawnTrailEffect(const FVector& EndPoint) { if (TrailFX) { const FVector Origin = GetMuzzleLocation(); UParticleSystemComponent* TrailPSC = UGameplayStatics::SpawnEmitterAtLocation(this, TrailFX, Origin); if (TrailPSC) { TrailPSC->SetVectorParameter(TrailTargetParam, EndPoint); } } }
void AFournoidWeapon::PlayFireAnimation() { if ( FireAnimation ) { auto AnimInstance = Mesh1P->GetAnimInstance(); if ( AnimInstance ) { AnimInstance->Montage_Play(FireAnimation, 1.f); } } if ( FireEmitter ) { UGameplayStatics::SpawnEmitterAtLocation(this, FireEmitter, GetMuzzleLocation()); } }
void AShooterWeapon_Instant::ServerNotifyMiss_Implementation(FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread) { const FVector Origin = GetMuzzleLocation(); // play FX on remote clients HitNotify.Origin = Origin; HitNotify.RandomSeed = RandomSeed; HitNotify.ReticleSpread = ReticleSpread; // play FX locally if (GetNetMode() != NM_DedicatedServer) { const FVector EndTrace = Origin + ShootDir * InstantConfig.WeaponRange; SpawnTrailEffect(EndTrace); } }
void ASWeaponInstant::SimulateInstantHit(const FVector& ImpactPoint) { const FVector MuzzleOrigin = GetMuzzleLocation(); /* Adjust direction based on desired crosshair impact point and muzzle location */ const FVector AimDir = (ImpactPoint - MuzzleOrigin).GetSafeNormal(); const FVector EndTrace = MuzzleOrigin + (AimDir * WeaponRange); const FHitResult Impact = WeaponTrace(MuzzleOrigin, EndTrace); if (Impact.bBlockingHit) { SpawnImpactEffects(Impact); SpawnTrailEffects(Impact.ImpactPoint); } else { SpawnTrailEffects(EndTrace); } }
void ASWeaponInstant::SpawnTrailEffects(const FVector& EndPoint) { // Keep local count for effects BulletsShotCount++; const FVector Origin = GetMuzzleLocation(); FVector ShootDir = EndPoint - Origin; // Only spawn if a minimum distance is satisfied. if (ShootDir.Size() < MinimumProjectileSpawnDistance) { return; } if (BulletsShotCount % TracerRoundInterval == 0) { if (TracerFX) { ShootDir.Normalize(); UGameplayStatics::SpawnEmitterAtLocation(this, TracerFX, Origin, ShootDir.Rotation()); } } else { // Only create trails FX by other players. ASCharacter* OwningPawn = GetPawnOwner(); if (OwningPawn && OwningPawn->IsLocallyControlled()) { return; } if (TrailFX) { UParticleSystemComponent* TrailPSC = UGameplayStatics::SpawnEmitterAtLocation(this, TrailFX, Origin); if (TrailPSC) { TrailPSC->SetVectorParameter(TrailTargetParam, EndPoint); } } } }
FVector AShooterWeapon::GetCameraDamageStartLocation(const FVector& AimDir) const { AShooterPlayerController* PC = MyPawn ? Cast<AShooterPlayerController>(MyPawn->Controller) : NULL; AShooterAIController* AIPC = MyPawn ? Cast<AShooterAIController>(MyPawn->Controller) : NULL; FVector OutStartTrace = FVector::ZeroVector; if (PC) { // use player's camera FRotator UnusedRot; PC->GetPlayerViewPoint(OutStartTrace, UnusedRot); // Adjust trace so there is nothing blocking the ray between the camera and the pawn, and calculate distance from adjusted start OutStartTrace = OutStartTrace + AimDir * ((Instigator->GetActorLocation() - OutStartTrace) | AimDir); } else if (AIPC) { OutStartTrace = GetMuzzleLocation(); } return OutStartTrace; }
void AShooterWeapon_Instant::ServerNotifyHit_Implementation(const FHitResult Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread) { const float WeaponAngleDot = FMath::Abs(FMath::Sin(ReticleSpread * PI / 180.f)); // if we have an instigator, calculate dot between the view and the shot if (Instigator && (Impact.GetActor() || Impact.bBlockingHit)) { const FVector Origin = GetMuzzleLocation(); const FVector ViewDir = (Impact.Location - Origin).GetSafeNormal(); // is the angle between the hit and the view within allowed limits (limit + weapon max angle) const float ViewDotHitDir = FVector::DotProduct(Instigator->GetViewRotation().Vector(), ViewDir); if (ViewDotHitDir > InstantConfig.AllowedViewDotHitDir - WeaponAngleDot) { if (CurrentState != EWeaponState::Idle) { if (Impact.GetActor() == NULL) { if (Impact.bBlockingHit) { ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread); } } // assume it told the truth about static things because the don't move and the hit // usually doesn't have significant gameplay implications else if (Impact.GetActor()->IsRootComponentStatic() || Impact.GetActor()->IsRootComponentStationary()) { ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread); } else { // Get the component bounding box const FBox HitBox = Impact.GetActor()->GetComponentsBoundingBox(); // calculate the box extent, and increase by a leeway FVector BoxExtent = 0.5 * (HitBox.Max - HitBox.Min); BoxExtent *= InstantConfig.ClientSideHitLeeway; // avoid precision errors with really thin objects BoxExtent.X = FMath::Max(20.0f, BoxExtent.X); BoxExtent.Y = FMath::Max(20.0f, BoxExtent.Y); BoxExtent.Z = FMath::Max(20.0f, BoxExtent.Z); // Get the box center const FVector BoxCenter = (HitBox.Min + HitBox.Max) * 0.5; // if we are within client tolerance if (FMath::Abs(Impact.Location.Z - BoxCenter.Z) < BoxExtent.Z && FMath::Abs(Impact.Location.X - BoxCenter.X) < BoxExtent.X && FMath::Abs(Impact.Location.Y - BoxCenter.Y) < BoxExtent.Y) { ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread); } else { UE_LOG(LogShooterWeapon, Log, TEXT("%s Rejected client side hit of %s (outside bounding box tolerance)"), *GetNameSafe(this), *GetNameSafe(Impact.GetActor())); } } } } else if (ViewDotHitDir <= InstantConfig.AllowedViewDotHitDir) { UE_LOG(LogShooterWeapon, Log, TEXT("%s Rejected client side hit of %s (facing too far from the hit direction)"), *GetNameSafe(this), *GetNameSafe(Impact.GetActor())); } else { UE_LOG(LogShooterWeapon, Log, TEXT("%s Rejected client side hit of %s"), *GetNameSafe(this), *GetNameSafe(Impact.GetActor())); } } }