// Copyright Epic Games, Inc. All Rights Reserved. // UE5 Modular Game Framework — BPC_DamageReceptionSystem Implementation #include "Weapons/BPC_DamageReceptionSystem.h" DEFINE_LOG_CATEGORY_STATIC(LogDamage, Log, All); UBPC_DamageReceptionSystem::UBPC_DamageReceptionSystem() { PrimaryComponentTick.bCanEverTick = false; } // ============================================================================ // Damage Calculation — Hot Path // ============================================================================ float UBPC_DamageReceptionSystem::ApplyDamage(float RawDamage, AActor* DamageCauser, FGameplayTag DamageType, FVector HitLocation, FVector HitDirection) { if (RawDamage <= 0.0f || !DamageType.IsValid()) { return 0.0f; } // Step 1: Calculate damage multiplier from modifiers. float Multiplier = GetDamageMultiplier(DamageType); // Step 2: Apply resistance. float Resistance = CalculateResistance(DamageType); float ResistedAmount = RawDamage * Resistance; // Step 3: Calculate final damage. float FinalDamage = (RawDamage * Multiplier) - ResistedAmount; FinalDamage = FMath::Max(FinalDamage, 0.0f); // No negative damage. // Check for flat reduction modifiers. for (const FDamageModifier& Mod : DamageModifiers) { if (Mod.DamageType == DamageType && Mod.bFlatReduction) { FinalDamage = FMath::Max(FinalDamage - Mod.FlatReduction, 1.0f); // Minimum 1 damage. } } // Step 4: Apply shield absorption if available. if (CachedShieldSystem) { // Shield absorbs damage before health. // FinalDamage = CachedShieldSystem->AbsorbDamage(FinalDamage); } // Step 5: Route to health system. if (CachedHealthSystem) { // CachedHealthSystem->ApplyHealthDamage(FinalDamage); } // Step 6: Evaluate hit reaction. EvaluateHitReaction(FinalDamage, DamageCauser, HitDirection); // Step 7: Broadcast. OnDamageReceived.Broadcast(RawDamage, FinalDamage, DamageCauser, DamageType, HitLocation); if (ResistedAmount > 0.0f) { OnDamageResisted.Broadcast(ResistedAmount, DamageType, FString::Printf(TEXT("Resistance: %.1f%%"), Resistance * 100.0f)); } UE_LOG(LogDamage, Verbose, TEXT("ApplyDamage — Raw: %.1f → Final: %.1f (Type: %s, Resist: %.1f%%)"), RawDamage, FinalDamage, *DamageType.GetTagName().ToString(), Resistance * 100.0f); return FinalDamage; } float UBPC_DamageReceptionSystem::CalculateResistance(FGameplayTag DamageType) const { if (!DamageType.IsValid()) { return 0.0f; } // Base resistance + equipment-specific bonuses. float TotalResistance = BaseResistance; if (EquipmentConfig) { // EquipmentConfig would provide per-damage-type resistance values. // TotalResistance += EquipmentConfig->GetResistance(DamageType); } return FMath::Clamp(TotalResistance, 0.0f, 1.0f); } float UBPC_DamageReceptionSystem::GetDamageMultiplier(FGameplayTag DamageType) const { if (!DamageType.IsValid()) { return 1.0f; } for (const FDamageModifier& Mod : DamageModifiers) { if (Mod.DamageType == DamageType && !Mod.bFlatReduction) { return Mod.Multiplier; } } return 1.0f; // No modifier — normal damage. } // ============================================================================ // Hit Reaction // ============================================================================ void UBPC_DamageReceptionSystem::EvaluateHitReaction(float FinalDamage, AActor* DamageCauser, FVector HitDirection) { if (FinalDamage >= KnockdownThreshold) { UE_LOG(LogDamage, Log, TEXT("EvaluateHitReaction — Knockdown! (%.1f damage)"), FinalDamage); OnKnockedDown.Broadcast(DamageCauser, FinalDamage); } else if (FinalDamage >= StaggerThreshold) { UE_LOG(LogDamage, Verbose, TEXT("EvaluateHitReaction — Stagger (%.1f damage)"), FinalDamage); OnStaggered.Broadcast(DamageCauser, FinalDamage); } // Route to dedicated hit reaction system for animation selection. if (CachedHitReactionSystem) { // CachedHitReactionSystem->PlayHitReaction(FinalDamage, HitDirection, DamageCauser); } }