diff --git a/COMBAT_SYSTEM_README.md b/COMBAT_SYSTEM_README.md new file mode 100644 index 0000000..d2d344d --- /dev/null +++ b/COMBAT_SYSTEM_README.md @@ -0,0 +1,206 @@ +# Valor Combat System + +This document explains how to use the combat system component that has been added to the Valor Ability System plugin. + +## Overview + +The combat system consists of several key components: + +1. **ValorCombatComponent** - Core combat functionality +2. **ValorCombatData** - Data assets for combat configuration +3. **ValorCombatAbility** - Base class for combat abilities +4. **ValorCombatCharacter** - Example character with full combat integration + +## Components + +### ValorCombatComponent + +The main combat component that handles: +- Health management +- Damage dealing and receiving +- Combat state management (attacking, blocking, dodging) +- Combat events and callbacks +- Integration with the Gameplay Ability System + +#### Key Features: +- **Health System**: Automatic health management with events +- **Damage System**: Configurable damage types and calculations +- **Combat States**: Track attacking, blocking, dodging states +- **Resource Management**: Stamina and mana consumption +- **Event System**: Blueprint-friendly events for UI and gameplay + +### ValorCombatData + +Data asset containing: +- Combat statistics (damage, defense, critical chance, etc.) +- Available combat actions +- Damage type tags +- Combat state tags + +### Combat Abilities + +Pre-built combat abilities: +- **Light Attack**: Quick, low-cost attack +- **Heavy Attack**: Powerful, high-cost attack with knockback +- **Block**: Damage reduction while active +- **Dodge**: Brief invincibility and movement + +## Usage + +### Basic Setup + +1. **Add to Character**: The `ValorCharacter` class already includes the combat component +2. **Configure Attributes**: Set up health, stamina, and mana in the attribute set +3. **Grant Abilities**: Add combat abilities to the character's ability system + +### Example Character Setup + +```cpp +// In your character's BeginPlay() +void AMyCharacter::BeginPlay() +{ + Super::BeginPlay(); + + // Get the combat component + UValorCombatComponent* CombatComp = GetCombatComponent(); + + // Bind to combat events + CombatComp->OnHealthChanged.AddDynamic(this, &AMyCharacter::OnHealthChanged); + CombatComp->OnCharacterDied.AddDynamic(this, &AMyCharacter::OnCharacterDied); + + // Grant combat abilities + if (AbilitySystemComponent) + { + FGameplayAbilitySpec LightAttackSpec(UValorLightAttackAbility::StaticClass(), 1, 0); + AbilitySystemComponent->GiveAbility(LightAttackSpec); + } +} +``` + +### Input Handling + +```cpp +// In SetupPlayerInputComponent() +void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + // Bind input actions to combat abilities + PlayerInputComponent->BindAction("LightAttack", IE_Pressed, this, &AMyCharacter::OnLightAttack); + PlayerInputComponent->BindAction("Block", IE_Pressed, this, &AMyCharacter::OnBlockPressed); + PlayerInputComponent->BindAction("Block", IE_Released, this, &AMyCharacter::OnBlockReleased); +} + +void AMyCharacter::OnLightAttack() +{ + if (AbilitySystemComponent) + { + FGameplayAbilitySpecHandle Handle = AbilitySystemComponent->FindAbilitySpecFromClass(UValorLightAttackAbility::StaticClass()); + AbilitySystemComponent->TryActivateAbility(Handle); + } +} +``` + +### Blueprint Integration + +The combat system is fully Blueprint-accessible: + +1. **Events**: All combat events can be bound in Blueprint +2. **Functions**: All combat functions are Blueprint-callable +3. **Properties**: All combat properties are Blueprint-editable + +### Custom Combat Abilities + +To create custom combat abilities: + +1. Inherit from `UValorCombatAbility` +2. Override `ActivateAbility` to implement your logic +3. Use the provided helper functions for damage, targeting, etc. + +```cpp +UCLASS() +class MYGAME_API UMyCustomAbility : public UValorCombatAbility +{ + GENERATED_BODY() + +public: + UMyCustomAbility(); + +protected: + virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override + { + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); + + // Your custom combat logic here + AActor* Target = GetPrimaryTarget(); + if (Target) + { + FDamageInfo DamageInfo; + DamageInfo.Damage = 50.0f; + DamageInfo.DamageType = EDamageType::Magical; + ApplyDamage(Target, DamageInfo); + } + } +}; +``` + +## Configuration + +### Combat Data Asset + +Create a `ValorCombatData` asset to configure: +- Base combat stats +- Available actions +- Damage type mappings +- Combat state tags + +### Attribute Setup + +Ensure your character has the required attributes: +- Health / MaxHealth +- Stamina / MaxStamina +- Mana / MaxMana +- Strength, Dexterity, Intelligence + +## Events + +The combat system provides several events for integration: + +- `OnHealthChanged`: When health changes +- `OnCharacterDied`: When character dies +- `OnDamageDealt`: When damage is dealt +- `OnDamageReceived`: When damage is received + +## Best Practices + +1. **Use Data Assets**: Configure combat stats through data assets for easy balancing +2. **Event-Driven**: Use combat events for UI updates and gameplay responses +3. **Resource Management**: Always check stamina/mana before activating abilities +4. **State Management**: Use combat states to prevent invalid actions +5. **Blueprint Integration**: Expose key functions to Blueprint for designers + +## Troubleshooting + +### Common Issues + +1. **Abilities Not Activating**: Check if character has enough stamina/mana +2. **Damage Not Applying**: Ensure target has a combat component +3. **Events Not Firing**: Verify event bindings are set up correctly +4. **Attributes Not Updating**: Check if attribute set is properly initialized + +### Debug Tips + +- Use `GetCombatComponent()` to access combat functionality +- Check `IsAlive()` before performing combat actions +- Use Blueprint debugger to trace ability activation +- Monitor attribute values in the attribute set + +## Future Enhancements + +Potential additions to the combat system: +- Faction system for enemy detection +- Status effects and buffs/debuffs +- Combo system for chaining attacks +- Area-of-effect abilities +- Projectile system integration +- Animation integration helpers \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Private/ValorCharacter.cpp b/Source/ValorAbilitySystem/Private/ValorCharacter.cpp index f15a837..81574de 100644 Binary files a/Source/ValorAbilitySystem/Private/ValorCharacter.cpp and b/Source/ValorAbilitySystem/Private/ValorCharacter.cpp differ diff --git a/Source/ValorAbilitySystem/Private/ValorCombatAbilities.cpp b/Source/ValorAbilitySystem/Private/ValorCombatAbilities.cpp new file mode 100644 index 0000000..473d3e4 --- /dev/null +++ b/Source/ValorAbilitySystem/Private/ValorCombatAbilities.cpp @@ -0,0 +1,236 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#include "ValorCombatAbilities.h" +#include "ValorAbilitySystem/Public/ValorCombatComponent.h" +#include "ValorAbilitySystem/Public/ValorCharacter.h" +#include "ValorAbilitySystem/Public/GAS/ValorAttributeSet.h" +#include "GameFramework/Character.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "TimerManager.h" +#include "Kismet/KismetMathLibrary.h" + +// Light Attack Ability +UValorLightAttackAbility::UValorLightAttackAbility() +{ + // Set up action data + ActionData.ActionType = ECombatAction::LightAttack; + ActionData.DamageInfo.Damage = 10.0f; + ActionData.DamageInfo.DamageType = EDamageType::Physical; + ActionData.Duration = 0.5f; + ActionData.Cooldown = 1.0f; + ActionData.StaminaCost = 5.0f; +} + +void UValorLightAttackAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) +{ + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); + + // Get combat component + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, true); + return; + } + + // Start attack animation/state + CombatComp->StartAttack(); + + // Find target + AActor* Target = GetPrimaryTarget(); + if (Target && CombatComp->IsInRange(Target)) + { + // Calculate damage + float BaseDamage = CombatComp->GetAttackDamage() * DamageMultiplier; + + // Create damage info + FDamageInfo DamageInfo; + DamageInfo.Damage = BaseDamage; + DamageInfo.DamageType = EDamageType::Physical; + DamageInfo.bCanCrit = true; + + // Apply damage + ApplyDamage(Target, DamageInfo); + } + + // End ability after duration + FTimerHandle EndTimer; + GetWorld()->GetTimerManager().SetTimer(EndTimer, [this, Handle, ActorInfo, ActivationInfo]() + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, false); + }, ActionData.Duration, false); +} + +// Heavy Attack Ability +UValorHeavyAttackAbility::UValorHeavyAttackAbility() +{ + // Set up action data + ActionData.ActionType = ECombatAction::HeavyAttack; + ActionData.DamageInfo.Damage = 20.0f; + ActionData.DamageInfo.DamageType = EDamageType::Physical; + ActionData.Duration = 1.0f; + ActionData.Cooldown = 2.0f; + ActionData.StaminaCost = 15.0f; +} + +void UValorHeavyAttackAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) +{ + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); + + // Get combat component + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, true); + return; + } + + // Start attack animation/state + CombatComp->StartAttack(); + + // Find target + AActor* Target = GetPrimaryTarget(); + if (Target && CombatComp->IsInRange(Target)) + { + // Calculate damage + float BaseDamage = CombatComp->GetAttackDamage() * DamageMultiplier; + + // Create damage info + FDamageInfo DamageInfo; + DamageInfo.Damage = BaseDamage; + DamageInfo.DamageType = EDamageType::Physical; + DamageInfo.bCanCrit = true; + + // Apply damage + ApplyDamage(Target, DamageInfo); + + // Apply knockback if enabled + if (bCanKnockback) + { + // This would typically be handled by a gameplay effect or physics + // For now, we'll just log it + UE_LOG(LogTemp, Log, TEXT("Heavy Attack: Knockback applied to %s"), *Target->GetName()); + } + } + + // End ability after duration + FTimerHandle EndTimer; + GetWorld()->GetTimerManager().SetTimer(EndTimer, [this, Handle, ActorInfo, ActivationInfo]() + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, false); + }, ActionData.Duration, false); +} + +// Block Ability +UValorBlockAbility::UValorBlockAbility() +{ + // Set up action data + ActionData.ActionType = ECombatAction::Block; + ActionData.Duration = 0.0f; // Can be held + ActionData.Cooldown = 0.0f; + ActionData.StaminaCost = 2.0f; // Per second +} + +void UValorBlockAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) +{ + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); + + // Get combat component + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, true); + return; + } + + // Start blocking + CombatComp->StartBlock(); + + // Set up stamina drain timer + GetWorld()->GetTimerManager().SetTimer(BlockStaminaDrainTimer, [this, Handle, ActorInfo, ActivationInfo]() + { + // Check if we still have enough stamina + if (!HasEnoughStamina()) + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, false); + return; + } + + // Consume stamina + ConsumeStamina(); + + // Continue draining stamina + GetWorld()->GetTimerManager().SetTimer(BlockStaminaDrainTimer, [this, Handle, ActorInfo, ActivationInfo]() + { + // This will be called again in 1 second + }, 1.0f, false); + }, 1.0f, false); +} + +void UValorBlockAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) +{ + // Get combat component + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (CombatComp) + { + CombatComp->StopBlock(); + } + + // Clear stamina drain timer + GetWorld()->GetTimerManager().ClearTimer(BlockStaminaDrainTimer); + + Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); +} + +// Dodge Ability +UValorDodgeAbility::UValorDodgeAbility() +{ + // Set up action data + ActionData.ActionType = ECombatAction::Dodge; + ActionData.Duration = 0.3f; + ActionData.Cooldown = 1.5f; + ActionData.StaminaCost = 10.0f; +} + +void UValorDodgeAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) +{ + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); + + // Get combat component + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, true); + return; + } + + // Start dodging + CombatComp->StartDodge(); + + // Get character for movement + AActor* Owner = GetAvatarActorFromActorInfo(); + if (ACharacter* Character = Cast(Owner)) + { + // Calculate dodge direction (backward from current facing direction) + FVector DodgeDirection = -Character->GetActorForwardVector(); + FVector DodgeLocation = Character->GetActorLocation() + (DodgeDirection * DodgeDistance); + + // Move character + Character->SetActorLocation(DodgeLocation); + + // Set invincibility if enabled + if (bInvincibleDuringDodge) + { + // This would typically be handled by a gameplay effect + // For now, we'll just log it + UE_LOG(LogTemp, Log, TEXT("Dodge: Invincibility active for %f seconds"), DodgeDuration); + } + } + + // End ability after duration + FTimerHandle EndTimer; + GetWorld()->GetTimerManager().SetTimer(EndTimer, [this, Handle, ActorInfo, ActivationInfo]() + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, false); + }, DodgeDuration, false); +} \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Private/ValorCombatAbility.cpp b/Source/ValorAbilitySystem/Private/ValorCombatAbility.cpp new file mode 100644 index 0000000..59e2cf0 --- /dev/null +++ b/Source/ValorAbilitySystem/Private/ValorCombatAbility.cpp @@ -0,0 +1,269 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#include "ValorCombatAbility.h" +#include "ValorAbilitySystem/Public/ValorCombatComponent.h" +#include "ValorAbilitySystem/Public/ValorCharacter.h" +#include "ValorAbilitySystem/Public/GAS/ValorAttributeSet.h" +#include "GameplayTagsModule.h" +#include "Kismet/GameplayStatics.h" + +UValorCombatAbility::UValorCombatAbility() +{ + // Set default ability settings + InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; + NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted; + ActivationBlockedTags.AddTag(FGameplayTag::RequestGameplayTag(FName("CombatState.Dead"))); + ActivationBlockedTags.AddTag(FGameplayTag::RequestGameplayTag(FName("CombatState.Stunned"))); +} + +bool UValorCombatAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const +{ + if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags)) + { + return false; + } + + return CheckCombatConditions(); +} + +void UValorCombatAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) +{ + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); + + // Apply resource costs + ApplyResourceCosts(); + + // Call blueprint event + OnCombatAbilityActivated(); + + // If this is an instant ability, end it immediately + if (ActionData.Duration <= 0.0f) + { + EndAbility(Handle, ActorInfo, ActivationInfo, true, false); + } +} + +void UValorCombatAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) +{ + // Call blueprint event + if (bWasCancelled) + { + OnCombatAbilityCancelled(); + } + else + { + OnCombatAbilityEnded(); + } + + Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); +} + +UValorCombatComponent* UValorCombatAbility::GetCombatComponent() const +{ + AActor* Owner = GetAvatarActorFromActorInfo(); + if (AValorCharacter* ValorChar = Cast(Owner)) + { + return ValorChar->GetCombatComponent(); + } + return nullptr; +} + +bool UValorCombatAbility::HasEnoughStamina() const +{ + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + return false; + } + + UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo(); + if (!ASC) + { + return false; + } + + UValorAttributeSet* AttributeSet = ASC->GetSet(); + if (!AttributeSet) + { + return false; + } + + float CurrentStamina = AttributeSet->GetStamina(); + return CurrentStamina >= ActionData.StaminaCost; +} + +bool UValorCombatAbility::HasEnoughMana() const +{ + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + return false; + } + + UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo(); + if (!ASC) + { + return false; + } + + UValorAttributeSet* AttributeSet = ASC->GetSet(); + if (!AttributeSet) + { + return false; + } + + float CurrentMana = AttributeSet->GetMana(); + return CurrentMana >= ActionData.ManaCost; +} + +void UValorCombatAbility::ConsumeStamina() +{ + if (ActionData.StaminaCost <= 0.0f) + { + return; + } + + UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo(); + if (!ASC) + { + return; + } + + UValorAttributeSet* AttributeSet = ASC->GetSet(); + if (!AttributeSet) + { + return; + } + + // Apply stamina cost using gameplay effect + FGameplayEffectSpecHandle StaminaEffectSpec = ASC->MakeOutgoingSpec( + UValorAttributeSet::GetStaminaAttribute().GetUProperty(), 1.0f, ASC->MakeEffectContext()); + + if (StaminaEffectSpec.IsValid()) + { + FGameplayEffectSpec* Spec = StaminaEffectSpec.Data.Get(); + Spec->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.StaminaCost")), -ActionData.StaminaCost); + ASC->ApplyGameplayEffectSpecToSelf(*Spec); + } +} + +void UValorCombatAbility::ConsumeMana() +{ + if (ActionData.ManaCost <= 0.0f) + { + return; + } + + UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo(); + if (!ASC) + { + return; + } + + UValorAttributeSet* AttributeSet = ASC->GetSet(); + if (!AttributeSet) + { + return; + } + + // Apply mana cost using gameplay effect + FGameplayEffectSpecHandle ManaEffectSpec = ASC->MakeOutgoingSpec( + UValorAttributeSet::GetManaAttribute().GetUProperty(), 1.0f, ASC->MakeEffectContext()); + + if (ManaEffectSpec.IsValid()) + { + FGameplayEffectSpec* Spec = ManaEffectSpec.Data.Get(); + Spec->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.ManaCost")), -ActionData.ManaCost); + ASC->ApplyGameplayEffectSpecToSelf(*Spec); + } +} + +void UValorCombatAbility::ApplyDamage(AActor* Target, const FDamageInfo& DamageInfo) +{ + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp || !Target) + { + return; + } + + // Get the damage tag for the damage type + FGameplayTag DamageTag = FGameplayTag::EmptyTag; + if (DamageInfo.DamageTag.IsValid()) + { + DamageTag = DamageInfo.DamageTag; + } + else + { + // Use default damage tags based on type + switch (DamageInfo.DamageType) + { + case EDamageType::Physical: + DamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.Physical")); + break; + case EDamageType::Magical: + DamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.Magical")); + break; + case EDamageType::TrueDamage: + DamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.True")); + break; + default: + DamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.Physical")); + break; + } + } + + // Apply damage through combat component + CombatComp->DealDamage(Target, DamageInfo.Damage, DamageTag); +} + +TArray UValorCombatAbility::FindTargetsInRange(float Range) const +{ + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + return TArray(); + } + + return CombatComp->FindEnemiesInRange(Range); +} + +AActor* UValorCombatAbility::GetPrimaryTarget() const +{ + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp) + { + return nullptr; + } + + return CombatComp->FindNearestEnemy(CombatComp->AttackRange); +} + +bool UValorCombatAbility::CheckCombatConditions() const +{ + // Check if we have enough stamina + if (!HasEnoughStamina()) + { + return false; + } + + // Check if we have enough mana + if (!HasEnoughMana()) + { + return false; + } + + // Check if combat component is valid and character is alive + UValorCombatComponent* CombatComp = GetCombatComponent(); + if (!CombatComp || !CombatComp->IsAlive()) + { + return false; + } + + return true; +} + +void UValorCombatAbility::ApplyResourceCosts() +{ + ConsumeStamina(); + ConsumeMana(); +} \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Private/ValorCombatCharacter.cpp b/Source/ValorAbilitySystem/Private/ValorCombatCharacter.cpp new file mode 100644 index 0000000..03c687d --- /dev/null +++ b/Source/ValorAbilitySystem/Private/ValorCombatCharacter.cpp @@ -0,0 +1,185 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#include "ValorCombatCharacter.h" +#include "ValorAbilitySystem/Public/ValorCombatAbilities.h" +#include "ValorAbilitySystem/Public/ValorCombatComponent.h" +#include "ValorAbilitySystem/Public/ValorCombatData.h" +#include "Components/InputComponent.h" +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "GameplayTagsModule.h" + +AValorCombatCharacter::AValorCombatCharacter() +{ + // Set default values + PrimaryActorTick.bCanEverTick = true; +} + +void AValorCombatCharacter::BeginPlay() +{ + Super::BeginPlay(); + + // Bind combat events + BindCombatEvents(); + + // Grant combat abilities + GrantCombatAbilities(); +} + +void AValorCombatCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + // Set up input bindings for combat actions + if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) + { + if (LightAttackAction) + { + EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Triggered, this, &AValorCombatCharacter::OnLightAttackPressed); + } + + if (HeavyAttackAction) + { + EnhancedInputComponent->BindAction(HeavyAttackAction, ETriggerEvent::Triggered, this, &AValorCombatCharacter::OnHeavyAttackPressed); + } + + if (BlockAction) + { + EnhancedInputComponent->BindAction(BlockAction, ETriggerEvent::Triggered, this, &AValorCombatCharacter::OnBlockPressed); + EnhancedInputComponent->BindAction(BlockAction, ETriggerEvent::Completed, this, &AValorCombatCharacter::OnBlockReleased); + } + + if (DodgeAction) + { + EnhancedInputComponent->BindAction(DodgeAction, ETriggerEvent::Triggered, this, &AValorCombatCharacter::OnDodgePressed); + } + } +} + +void AValorCombatCharacter::OnLightAttackPressed() +{ + if (!CombatComponent || !AbilitySystemComponent) + { + return; + } + + // Try to activate light attack ability + FGameplayAbilitySpecHandle LightAttackHandle = AbilitySystemComponent->FindAbilitySpecFromClass(UValorLightAttackAbility::StaticClass()); + if (LightAttackHandle.IsValid()) + { + AbilitySystemComponent->TryActivateAbility(LightAttackHandle); + } +} + +void AValorCombatCharacter::OnHeavyAttackPressed() +{ + if (!CombatComponent || !AbilitySystemComponent) + { + return; + } + + // Try to activate heavy attack ability + FGameplayAbilitySpecHandle HeavyAttackHandle = AbilitySystemComponent->FindAbilitySpecFromClass(UValorHeavyAttackAbility::StaticClass()); + if (HeavyAttackHandle.IsValid()) + { + AbilitySystemComponent->TryActivateAbility(HeavyAttackHandle); + } +} + +void AValorCombatCharacter::OnBlockPressed() +{ + if (!CombatComponent || !AbilitySystemComponent) + { + return; + } + + // Try to activate block ability + FGameplayAbilitySpecHandle BlockHandle = AbilitySystemComponent->FindAbilitySpecFromClass(UValorBlockAbility::StaticClass()); + if (BlockHandle.IsValid()) + { + AbilitySystemComponent->TryActivateAbility(BlockHandle); + } +} + +void AValorCombatCharacter::OnBlockReleased() +{ + if (!CombatComponent || !AbilitySystemComponent) + { + return; + } + + // End block ability + FGameplayAbilitySpecHandle BlockHandle = AbilitySystemComponent->FindAbilitySpecFromClass(UValorBlockAbility::StaticClass()); + if (BlockHandle.IsValid()) + { + AbilitySystemComponent->CancelAbility(BlockHandle); + } +} + +void AValorCombatCharacter::OnDodgePressed() +{ + if (!CombatComponent || !AbilitySystemComponent) + { + return; + } + + // Try to activate dodge ability + FGameplayAbilitySpecHandle DodgeHandle = AbilitySystemComponent->FindAbilitySpecFromClass(UValorDodgeAbility::StaticClass()); + if (DodgeHandle.IsValid()) + { + AbilitySystemComponent->TryActivateAbility(DodgeHandle); + } +} + +void AValorCombatCharacter::BindCombatEvents() +{ + if (!CombatComponent) + { + return; + } + + // Bind to combat component events + CombatComponent->OnHealthChanged.AddDynamic(this, &AValorCombatCharacter::OnHealthChangedEvent); + CombatComponent->OnCharacterDied.AddDynamic(this, &AValorCombatCharacter::OnCharacterDiedEvent); + CombatComponent->OnDamageDealt.AddDynamic(this, &AValorCombatCharacter::OnDamageDealtEvent); + CombatComponent->OnDamageReceived.AddDynamic(this, &AValorCombatCharacter::OnDamageReceivedEvent); +} + +void AValorCombatCharacter::UnbindCombatEvents() +{ + if (!CombatComponent) + { + return; + } + + // Unbind from combat component events + CombatComponent->OnHealthChanged.RemoveDynamic(this, &AValorCombatCharacter::OnHealthChangedEvent); + CombatComponent->OnCharacterDied.RemoveDynamic(this, &AValorCombatCharacter::OnCharacterDiedEvent); + CombatComponent->OnDamageDealt.RemoveDynamic(this, &AValorCombatCharacter::OnDamageDealtEvent); + CombatComponent->OnDamageReceived.RemoveDynamic(this, &AValorCombatCharacter::OnDamageReceivedEvent); +} + +void AValorCombatCharacter::GrantCombatAbilities() +{ + if (!AbilitySystemComponent) + { + return; + } + + // Grant combat abilities + FGameplayAbilitySpec LightAttackSpec(UValorLightAttackAbility::StaticClass(), 1, 0); + FGameplayAbilitySpecHandle LightAttackHandle = AbilitySystemComponent->GiveAbility(LightAttackSpec); + CombatAbilityHandles.Add(LightAttackHandle); + + FGameplayAbilitySpec HeavyAttackSpec(UValorHeavyAttackAbility::StaticClass(), 1, 0); + FGameplayAbilitySpecHandle HeavyAttackHandle = AbilitySystemComponent->GiveAbility(HeavyAttackSpec); + CombatAbilityHandles.Add(HeavyAttackHandle); + + FGameplayAbilitySpec BlockSpec(UValorBlockAbility::StaticClass(), 1, 0); + FGameplayAbilitySpecHandle BlockHandle = AbilitySystemComponent->GiveAbility(BlockSpec); + CombatAbilityHandles.Add(BlockHandle); + + FGameplayAbilitySpec DodgeSpec(UValorDodgeAbility::StaticClass(), 1, 0); + FGameplayAbilitySpecHandle DodgeHandle = AbilitySystemComponent->GiveAbility(DodgeSpec); + CombatAbilityHandles.Add(DodgeHandle); +} \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Private/ValorCombatComponent.cpp b/Source/ValorAbilitySystem/Private/ValorCombatComponent.cpp new file mode 100644 index 0000000..330a440 --- /dev/null +++ b/Source/ValorAbilitySystem/Private/ValorCombatComponent.cpp @@ -0,0 +1,482 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#include "ValorCombatComponent.h" +#include "ValorAbilitySystem/Public/ValorCharacter.h" +#include "ValorAbilitySystem/Public/GAS/ValorAttributeSet.h" +#include "GameFramework/Character.h" +#include "GameplayTagsModule.h" +#include "Engine/World.h" +#include "TimerManager.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerController.h" +#include "Components/PrimitiveComponent.h" + +UValorCombatComponent::UValorCombatComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + + // Initialize damage type tags + PhysicalDamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.Physical")); + MagicalDamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.Magical")); + TrueDamageTag = FGameplayTag::RequestGameplayTag(FName("Damage.True")); +} + +void UValorCombatComponent::BeginPlay() +{ + Super::BeginPlay(); + + InitializeReferences(); + UpdateHealthDisplay(); +} + +void UValorCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Update combat state + if (bIsAlive) + { + CheckDeath(); + } +} + +void UValorCombatComponent::InitializeReferences() +{ + // Get references to the owner's ability system components + AActor* Owner = GetOwner(); + if (AValorCharacter* ValorChar = Cast(Owner)) + { + AbilitySystemComponent = ValorChar->GetAbilitySystemComponent(); + if (AbilitySystemComponent) + { + AttributeSet = AbilitySystemComponent->GetSet(); + } + } +} + +void UValorCombatComponent::UpdateHealthDisplay() +{ + if (AttributeSet) + { + float CurrentHealth = GetCurrentHealth(); + float MaxHealth = GetMaxHealth(); + float Percentage = MaxHealth > 0.0f ? CurrentHealth / MaxHealth : 0.0f; + + OnHealthChanged.Broadcast(CurrentHealth, MaxHealth, Percentage); + } +} + +void UValorCombatComponent::CheckDeath() +{ + if (AttributeSet && bIsAlive) + { + float CurrentHealth = GetCurrentHealth(); + if (CurrentHealth <= 0.0f) + { + Die(); + } + } +} + +float UValorCombatComponent::CalculateDamage(float BaseDamage, FGameplayTag DamageType) const +{ + if (!AttributeSet) + { + return BaseDamage; + } + + float FinalDamage = BaseDamage; + + // Apply strength modifier for physical damage + if (DamageType == PhysicalDamageTag) + { + float Strength = AttributeSet->GetStrength(); + FinalDamage += Strength * 0.1f; // 10% damage per strength point + } + // Apply intelligence modifier for magical damage + else if (DamageType == MagicalDamageTag) + { + float Intelligence = AttributeSet->GetIntelligence(); + FinalDamage += Intelligence * 0.1f; // 10% damage per intelligence point + } + + return FMath::Max(0.0f, FinalDamage); +} + +float UValorCombatComponent::CalculateDefense(FGameplayTag DamageType) const +{ + if (!AttributeSet) + { + return 0.0f; + } + + float Defense = 0.0f; + + // Apply dexterity modifier for physical defense + if (DamageType == PhysicalDamageTag) + { + float Dexterity = AttributeSet->GetDexterity(); + Defense += Dexterity * 0.05f; // 5% damage reduction per dexterity point + } + // Apply intelligence modifier for magical defense + else if (DamageType == MagicalDamageTag) + { + float Intelligence = AttributeSet->GetIntelligence(); + Defense += Intelligence * 0.05f; // 5% damage reduction per intelligence point + } + + return FMath::Clamp(Defense, 0.0f, 0.95f); // Cap at 95% damage reduction +} + +bool UValorCombatComponent::IsEnemy(AActor* Target) const +{ + if (!Target || Target == GetOwner()) + { + return false; + } + + // Simple enemy detection - can be expanded with faction system + // For now, check if the target has a different controller + APawn* OwnerPawn = Cast(GetOwner()); + APawn* TargetPawn = Cast(Target); + + if (OwnerPawn && TargetPawn) + { + return OwnerPawn->GetController() != TargetPawn->GetController(); + } + + return false; +} + +// Combat State Functions +bool UValorCombatComponent::CanAttack() const +{ + return bIsAlive && !bIsAttacking && !bIsDodging && + (GetWorld()->GetTimeSeconds() - LastAttackTime) >= AttackCooldown; +} + +bool UValorCombatComponent::CanBlock() const +{ + return bIsAlive && !bIsAttacking && !bIsDodging && !bIsBlocking; +} + +bool UValorCombatComponent::CanDodge() const +{ + return bIsAlive && !bIsAttacking && !bIsBlocking && !bIsDodging; +} + +void UValorCombatComponent::StartAttack() +{ + if (CanAttack()) + { + bIsAttacking = true; + LastAttackTime = GetWorld()->GetTimeSeconds(); + + // Set a timer to stop attacking after a short duration + GetWorld()->GetTimerManager().SetTimer(AttackCooldownTimer, this, &UValorCombatComponent::StopAttack, 0.5f, false); + } +} + +void UValorCombatComponent::StopAttack() +{ + bIsAttacking = false; +} + +void UValorCombatComponent::StartBlock() +{ + if (CanBlock()) + { + bIsBlocking = true; + } +} + +void UValorCombatComponent::StopBlock() +{ + bIsBlocking = false; +} + +void UValorCombatComponent::StartDodge() +{ + if (CanDodge()) + { + bIsDodging = true; + + // Set a timer to stop dodging after a short duration + GetWorld()->GetTimerManager().SetTimer(DodgeTimer, this, &UValorCombatComponent::StopDodge, 0.3f, false); + } +} + +void UValorCombatComponent::StopDodge() +{ + bIsDodging = false; +} + +// Damage Functions +void UValorCombatComponent::DealDamage(AActor* Target, float Damage, FGameplayTag DamageType) +{ + if (!Target || !bIsAlive || !IsEnemy(Target)) + { + return; + } + + // Calculate final damage + float FinalDamage = CalculateDamage(Damage, DamageType); + + // Apply damage to target + if (UValorCombatComponent* TargetCombat = Target->FindComponentByClass()) + { + TargetCombat->TakeDamage(GetOwner(), FinalDamage, DamageType); + } + + // Broadcast damage dealt event + OnDamageDealt.Broadcast(Target, FinalDamage, DamageType); +} + +void UValorCombatComponent::DealPhysicalDamage(AActor* Target, float Damage) +{ + // Use physical damage as default + DealDamage(Target, Damage, PhysicalDamageTag); +} + +void UValorCombatComponent::TakeDamage(AActor* Source, float Damage, FGameplayTag DamageType) +{ + if (!bCanTakeDamage || !bIsAlive || !AttributeSet) + { + return; + } + + // Apply block damage reduction + if (bIsBlocking) + { + Damage *= (1.0f - BlockDamageReduction); + } + + // Apply defense + float Defense = CalculateDefense(DamageType); + Damage *= (1.0f - Defense); + + // Apply damage to health + float CurrentHealth = GetCurrentHealth(); + float NewHealth = FMath::Max(0.0f, CurrentHealth - Damage); + + // Use gameplay effect to modify health attribute + if (AbilitySystemComponent) + { + FGameplayEffectSpecHandle DamageEffectSpec = AbilitySystemComponent->MakeOutgoingSpec( + UValorAttributeSet::GetHealthAttribute().GetUProperty(), 1.0f, AbilitySystemComponent->MakeEffectContext()); + + if (DamageEffectSpec.IsValid()) + { + FGameplayEffectSpec* Spec = DamageEffectSpec.Data.Get(); + Spec->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.Damage")), -Damage); + AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*Spec); + } + } + + // Broadcast damage received event + OnDamageReceived.Broadcast(Source, Damage, DamageType); + + // Update health display + UpdateHealthDisplay(); +} + +void UValorCombatComponent::TakePhysicalDamage(AActor* Source, float Damage) +{ + // Use physical damage as default + TakeDamage(Source, Damage, PhysicalDamageTag); +} + +void UValorCombatComponent::Heal(float HealAmount) +{ + if (!bIsAlive || !AttributeSet || HealAmount <= 0.0f) + { + return; + } + + float CurrentHealth = GetCurrentHealth(); + float MaxHealth = GetMaxHealth(); + float NewHealth = FMath::Min(MaxHealth, CurrentHealth + HealAmount); + + // Use gameplay effect to modify health attribute + if (AbilitySystemComponent) + { + FGameplayEffectSpecHandle HealEffectSpec = AbilitySystemComponent->MakeOutgoingSpec( + UValorAttributeSet::GetHealthAttribute().GetUProperty(), 1.0f, AbilitySystemComponent->MakeEffectContext()); + + if (HealEffectSpec.IsValid()) + { + FGameplayEffectSpec* Spec = HealEffectSpec.Data.Get(); + Spec->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.Healing")), HealAmount); + AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*Spec); + } + } + + UpdateHealthDisplay(); +} + +void UValorCombatComponent::Die(AActor* Killer) +{ + if (!bIsAlive) + { + return; + } + + bIsAlive = false; + bIsAttacking = false; + bIsBlocking = false; + bIsDodging = false; + + // Broadcast death event + OnCharacterDied.Broadcast(GetOwner(), Killer); +} + +void UValorCombatComponent::Revive(float HealthPercentage) +{ + if (bIsAlive || !AttributeSet) + { + return; + } + + bIsAlive = true; + + float MaxHealth = GetMaxHealth(); + float ReviveHealth = MaxHealth * FMath::Clamp(HealthPercentage, 0.0f, 1.0f); + + // Set health to revive amount + if (AbilitySystemComponent) + { + FGameplayEffectSpecHandle ReviveEffectSpec = AbilitySystemComponent->MakeOutgoingSpec( + UValorAttributeSet::GetHealthAttribute().GetUProperty(), 1.0f, AbilitySystemComponent->MakeEffectContext()); + + if (ReviveEffectSpec.IsValid()) + { + FGameplayEffectSpec* Spec = ReviveEffectSpec.Data.Get(); + Spec->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.Revive")), ReviveHealth); + AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*Spec); + } + } + + UpdateHealthDisplay(); +} + +// Health Management +float UValorCombatComponent::GetCurrentHealth() const +{ + return AttributeSet ? AttributeSet->GetHealth() : 0.0f; +} + +float UValorCombatComponent::GetMaxHealth() const +{ + return AttributeSet ? AttributeSet->GetMaxHealth() : 0.0f; +} + +float UValorCombatComponent::GetHealthPercentage() const +{ + float MaxHealth = GetMaxHealth(); + return MaxHealth > 0.0f ? GetCurrentHealth() / MaxHealth : 0.0f; +} + +bool UValorCombatComponent::IsAlive() const +{ + return bIsAlive && GetCurrentHealth() > 0.0f; +} + +// Combat Stats +float UValorCombatComponent::GetAttackDamage() const +{ + if (!AttributeSet) + { + return BaseDamage; + } + + float Strength = AttributeSet->GetStrength(); + return BaseDamage + (Strength * 0.1f); +} + +float UValorCombatComponent::GetAttackSpeed() const +{ + if (!AttributeSet) + { + return 1.0f / AttackCooldown; + } + + float Dexterity = AttributeSet->GetDexterity(); + float SpeedMultiplier = 1.0f + (Dexterity * 0.02f); // 2% speed increase per dexterity point + return (1.0f / AttackCooldown) * SpeedMultiplier; +} + +float UValorCombatComponent::GetDefense() const +{ + if (!AttributeSet) + { + return 0.0f; + } + + float Dexterity = AttributeSet->GetDexterity(); + return Dexterity * 0.05f; // 5% damage reduction per dexterity point +} + +// Utility Functions +bool UValorCombatComponent::IsInRange(AActor* Target) const +{ + if (!Target || !GetOwner()) + { + return false; + } + + float Distance = FVector::Dist(GetOwner()->GetActorLocation(), Target->GetActorLocation()); + return Distance <= AttackRange; +} + +AActor* UValorCombatComponent::FindNearestEnemy(float SearchRadius) const +{ + TArray Enemies = FindEnemiesInRange(SearchRadius); + + if (Enemies.Num() == 0) + { + return nullptr; + } + + AActor* NearestEnemy = nullptr; + float NearestDistance = FLT_MAX; + + for (AActor* Enemy : Enemies) + { + float Distance = FVector::Dist(GetOwner()->GetActorLocation(), Enemy->GetActorLocation()); + if (Distance < NearestDistance) + { + NearestDistance = Distance; + NearestEnemy = Enemy; + } + } + + return NearestEnemy; +} + +TArray UValorCombatComponent::FindEnemiesInRange(float SearchRadius) const +{ + TArray Enemies; + + if (!GetOwner()) + { + return Enemies; + } + + // Find all actors in range + TArray ActorsInRange; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), ActorsInRange); + + for (AActor* Actor : ActorsInRange) + { + if (IsEnemy(Actor)) + { + float Distance = FVector::Dist(GetOwner()->GetActorLocation(), Actor->GetActorLocation()); + if (Distance <= SearchRadius) + { + Enemies.Add(Actor); + } + } + } + + return Enemies; +} \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Private/ValorCombatData.cpp b/Source/ValorAbilitySystem/Private/ValorCombatData.cpp new file mode 100644 index 0000000..6a7d8a5 --- /dev/null +++ b/Source/ValorAbilitySystem/Private/ValorCombatData.cpp @@ -0,0 +1,101 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#include "ValorCombatData.h" +#include "GameplayTagsModule.h" + +UValorCombatData::UValorCombatData() +{ + // Initialize default damage type tags + DamageTypeTags.Add(EDamageType::Physical, FGameplayTag::RequestGameplayTag(FName("Damage.Physical"))); + DamageTypeTags.Add(EDamageType::Magical, FGameplayTag::RequestGameplayTag(FName("Damage.Magical"))); + DamageTypeTags.Add(EDamageType::TrueDamage, FGameplayTag::RequestGameplayTag(FName("Damage.True"))); + DamageTypeTags.Add(EDamageType::Fire, FGameplayTag::RequestGameplayTag(FName("Damage.Fire"))); + DamageTypeTags.Add(EDamageType::Ice, FGameplayTag::RequestGameplayTag(FName("Damage.Ice"))); + DamageTypeTags.Add(EDamageType::Lightning, FGameplayTag::RequestGameplayTag(FName("Damage.Lightning"))); + DamageTypeTags.Add(EDamageType::Poison, FGameplayTag::RequestGameplayTag(FName("Damage.Poison"))); + DamageTypeTags.Add(EDamageType::Holy, FGameplayTag::RequestGameplayTag(FName("Damage.Holy"))); + DamageTypeTags.Add(EDamageType::Dark, FGameplayTag::RequestGameplayTag(FName("Damage.Dark"))); + + // Initialize default combat state tags + CombatStateTags.Add(ECombatState::Idle, FGameplayTag::RequestGameplayTag(FName("CombatState.Idle"))); + CombatStateTags.Add(ECombatState::Attacking, FGameplayTag::RequestGameplayTag(FName("CombatState.Attacking"))); + CombatStateTags.Add(ECombatState::Blocking, FGameplayTag::RequestGameplayTag(FName("CombatState.Blocking"))); + CombatStateTags.Add(ECombatState::Dodging, FGameplayTag::RequestGameplayTag(FName("CombatState.Dodging"))); + CombatStateTags.Add(ECombatState::Stunned, FGameplayTag::RequestGameplayTag(FName("CombatState.Stunned"))); + CombatStateTags.Add(ECombatState::Dead, FGameplayTag::RequestGameplayTag(FName("CombatState.Dead"))); + + // Initialize default combat actions + FCombatActionData LightAttack; + LightAttack.ActionType = ECombatAction::LightAttack; + LightAttack.DamageInfo.Damage = 10.0f; + LightAttack.DamageInfo.DamageType = EDamageType::Physical; + LightAttack.Duration = 0.5f; + LightAttack.Cooldown = 1.0f; + LightAttack.StaminaCost = 5.0f; + AvailableActions.Add(LightAttack); + + FCombatActionData HeavyAttack; + HeavyAttack.ActionType = ECombatAction::HeavyAttack; + HeavyAttack.DamageInfo.Damage = 20.0f; + HeavyAttack.DamageInfo.DamageType = EDamageType::Physical; + HeavyAttack.Duration = 1.0f; + HeavyAttack.Cooldown = 2.0f; + HeavyAttack.StaminaCost = 15.0f; + AvailableActions.Add(HeavyAttack); + + FCombatActionData Block; + Block.ActionType = ECombatAction::Block; + Block.Duration = 0.0f; // Can be held + Block.Cooldown = 0.0f; + Block.StaminaCost = 2.0f; // Per second + AvailableActions.Add(Block); + + FCombatActionData Dodge; + Dodge.ActionType = ECombatAction::Dodge; + Dodge.Duration = 0.3f; + Dodge.Cooldown = 1.5f; + Dodge.StaminaCost = 10.0f; + AvailableActions.Add(Dodge); +} + +FGameplayTag UValorCombatData::GetDamageTypeTag(EDamageType DamageType) const +{ + if (const FGameplayTag* FoundTag = DamageTypeTags.Find(DamageType)) + { + return *FoundTag; + } + return FGameplayTag::EmptyTag; +} + +FGameplayTag UValorCombatData::GetCombatStateTag(ECombatState CombatState) const +{ + if (const FGameplayTag* FoundTag = CombatStateTags.Find(CombatState)) + { + return *FoundTag; + } + return FGameplayTag::EmptyTag; +} + +FCombatActionData UValorCombatData::GetActionData(ECombatAction ActionType) const +{ + for (const FCombatActionData& Action : AvailableActions) + { + if (Action.ActionType == ActionType) + { + return Action; + } + } + return FCombatActionData(); +} + +bool UValorCombatData::HasAction(ECombatAction ActionType) const +{ + for (const FCombatActionData& Action : AvailableActions) + { + if (Action.ActionType == ActionType) + { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Public/ValorCharacter.h b/Source/ValorAbilitySystem/Public/ValorCharacter.h index 38f30ea..2d7dba7 100644 Binary files a/Source/ValorAbilitySystem/Public/ValorCharacter.h and b/Source/ValorAbilitySystem/Public/ValorCharacter.h differ diff --git a/Source/ValorAbilitySystem/Public/ValorCombatAbilities.h b/Source/ValorAbilitySystem/Public/ValorCombatAbilities.h new file mode 100644 index 0000000..a54f0d8 --- /dev/null +++ b/Source/ValorAbilitySystem/Public/ValorCombatAbilities.h @@ -0,0 +1,113 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ValorAbilitySystem/Public/ValorCombatAbility.h" +#include "ValorCombatAbilities.generated.h" + +/** + * UValorLightAttackAbility + * + * Basic light attack ability that deals moderate damage with low stamina cost. + */ +UCLASS(BlueprintType, Blueprintable) +class VALORABILITYSYSTEM_API UValorLightAttackAbility : public UValorCombatAbility +{ + GENERATED_BODY() + +public: + UValorLightAttackAbility(); + +protected: + virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; + +private: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Light Attack", meta = (AllowPrivateAccess = "true")) + float DamageMultiplier = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Light Attack", meta = (AllowPrivateAccess = "true")) + float Range = 200.0f; +}; + +/** + * UValorHeavyAttackAbility + * + * Heavy attack ability that deals high damage but costs more stamina and has longer cooldown. + */ +UCLASS(BlueprintType, Blueprintable) +class VALORABILITYSYSTEM_API UValorHeavyAttackAbility : public UValorCombatAbility +{ + GENERATED_BODY() + +public: + UValorHeavyAttackAbility(); + +protected: + virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; + +private: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Heavy Attack", meta = (AllowPrivateAccess = "true")) + float DamageMultiplier = 2.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Heavy Attack", meta = (AllowPrivateAccess = "true")) + float Range = 250.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Heavy Attack", meta = (AllowPrivateAccess = "true")) + bool bCanKnockback = true; +}; + +/** + * UValorBlockAbility + * + * Block ability that reduces incoming damage while active. + */ +UCLASS(BlueprintType, Blueprintable) +class VALORABILITYSYSTEM_API UValorBlockAbility : public UValorCombatAbility +{ + GENERATED_BODY() + +public: + UValorBlockAbility(); + +protected: + virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; + + virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override; + +private: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Block", meta = (AllowPrivateAccess = "true")) + float BlockReduction = 0.5f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Block", meta = (AllowPrivateAccess = "true")) + float StaminaDrainPerSecond = 2.0f; + + FTimerHandle BlockStaminaDrainTimer; +}; + +/** + * UValorDodgeAbility + * + * Dodge ability that provides brief invincibility and movement. + */ +UCLASS(BlueprintType, Blueprintable) +class VALORABILITYSYSTEM_API UValorDodgeAbility : public UValorCombatAbility +{ + GENERATED_BODY() + +public: + UValorDodgeAbility(); + +protected: + virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; + +private: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dodge", meta = (AllowPrivateAccess = "true")) + float DodgeDistance = 300.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dodge", meta = (AllowPrivateAccess = "true")) + float DodgeDuration = 0.3f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dodge", meta = (AllowPrivateAccess = "true")) + bool bInvincibleDuringDodge = true; +}; \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Public/ValorCombatAbility.h b/Source/ValorAbilitySystem/Public/ValorCombatAbility.h new file mode 100644 index 0000000..7b6dbc3 --- /dev/null +++ b/Source/ValorAbilitySystem/Public/ValorCombatAbility.h @@ -0,0 +1,86 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Abilities/GameplayAbility.h" +#include "ValorAbilitySystem/Public/ValorCombatData.h" +#include "ValorCombatAbility.generated.h" + +class UValorCombatComponent; + +/** + * UValorCombatAbility + * + * Base class for combat abilities that integrate with the Valor Combat System. + * Provides common functionality for all combat-related abilities. + */ +UCLASS(BlueprintType, Blueprintable) +class VALORABILITYSYSTEM_API UValorCombatAbility : public UGameplayAbility +{ + GENERATED_BODY() + +public: + UValorCombatAbility(); + + // Combat Action Data + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Ability") + FCombatActionData ActionData; + + // Override to check combat-specific conditions + virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const override; + + // Override to handle combat-specific activation + virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; + + // Override to handle combat-specific ending + virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override; + +protected: + // Get the combat component from the actor + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + UValorCombatComponent* GetCombatComponent() const; + + // Check if the actor has enough stamina for this ability + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + bool HasEnoughStamina() const; + + // Check if the actor has enough mana for this ability + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + bool HasEnoughMana() const; + + // Consume stamina for this ability + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + void ConsumeStamina(); + + // Consume mana for this ability + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + void ConsumeMana(); + + // Apply damage to a target + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + void ApplyDamage(AActor* Target, const FDamageInfo& DamageInfo); + + // Find targets in range + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + TArray FindTargetsInRange(float Range) const; + + // Get the primary target (for single-target abilities) + UFUNCTION(BlueprintCallable, Category = "Combat Ability") + AActor* GetPrimaryTarget() const; + + // Combat ability events + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Ability") + void OnCombatAbilityActivated(); + + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Ability") + void OnCombatAbilityEnded(); + + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Ability") + void OnCombatAbilityCancelled(); + +private: + // Internal helper functions + bool CheckCombatConditions() const; + void ApplyResourceCosts(); +}; \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Public/ValorCombatCharacter.h b/Source/ValorAbilitySystem/Public/ValorCombatCharacter.h new file mode 100644 index 0000000..f4822dd --- /dev/null +++ b/Source/ValorAbilitySystem/Public/ValorCombatCharacter.h @@ -0,0 +1,85 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ValorAbilitySystem/Public/ValorCharacter.h" +#include "ValorAbilitySystem/Public/ValorCombatData.h" +#include "ValorCombatCharacter.generated.h" + +class UValorCombatData; + +/** + * AValorCombatCharacter + * + * Extended character class that demonstrates the full integration of the combat system. + * This class shows how to use the combat component with input handling and ability management. + */ +UCLASS(Blueprintable, ClassGroup=(ValorAbilitySystem), meta=(BlueprintSpawnableComponent)) +class VALORABILITYSYSTEM_API AValorCombatCharacter : public AValorCharacter +{ + GENERATED_BODY() + +public: + AValorCombatCharacter(); + +protected: + virtual void BeginPlay() override; + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + // Combat Data Asset + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Configuration") + UValorCombatData* CombatData; + + // Input Actions + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") + class UInputAction* LightAttackAction; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") + class UInputAction* HeavyAttackAction; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") + class UInputAction* BlockAction; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") + class UInputAction* DodgeAction; + + // Input Handlers + UFUNCTION(BlueprintCallable, Category = "Input") + void OnLightAttackPressed(); + + UFUNCTION(BlueprintCallable, Category = "Input") + void OnHeavyAttackPressed(); + + UFUNCTION(BlueprintCallable, Category = "Input") + void OnBlockPressed(); + + UFUNCTION(BlueprintCallable, Category = "Input") + void OnBlockReleased(); + + UFUNCTION(BlueprintCallable, Category = "Input") + void OnDodgePressed(); + + // Combat Event Handlers + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Events") + void OnHealthChangedEvent(float CurrentHealth, float MaxHealth, float Percentage); + + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Events") + void OnCharacterDiedEvent(AActor* Character, AActor* Killer); + + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Events") + void OnDamageDealtEvent(AActor* Target, float Damage, FGameplayTag DamageType); + + UFUNCTION(BlueprintImplementableEvent, Category = "Combat Events") + void OnDamageReceivedEvent(AActor* Source, float Damage, FGameplayTag DamageType); + +private: + // Internal functions + void BindCombatEvents(); + void UnbindCombatEvents(); + void GrantCombatAbilities(); + + // Combat ability references + UPROPERTY() + TArray CombatAbilityHandles; +}; \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Public/ValorCombatComponent.h b/Source/ValorAbilitySystem/Public/ValorCombatComponent.h new file mode 100644 index 0000000..dcb905a --- /dev/null +++ b/Source/ValorAbilitySystem/Public/ValorCombatComponent.h @@ -0,0 +1,200 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameplayEffectTypes.h" +#include "GameplayTagContainer.h" +#include "ValorAbilitySystem/Public/GAS/ValorAttributeSet.h" +#include "ValorCombatComponent.generated.h" + +class UAbilitySystemComponent; +class UValorAttributeSet; + +// Forward declarations +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnHealthChanged, float, CurrentHealth, float, MaxHealth, float, Percentage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnCharacterDied, AActor*, Character, AActor*, Killer); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDamageDealt, AActor*, Target, float, Damage, FGameplayTag, DamageType); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDamageReceived, AActor*, Source, float, Damage, FGameplayTag, DamageType); + +/** + * UValorCombatComponent + * + * Combat system component that handles damage dealing, health management, and combat events. + * Integrates with the Gameplay Ability System for robust combat mechanics. + */ +UCLASS(Blueprintable, ClassGroup=(ValorAbilitySystem), meta=(BlueprintSpawnableComponent)) +class VALORABILITYSYSTEM_API UValorCombatComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UValorCombatComponent(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + // Combat Events + UPROPERTY(BlueprintAssignable, Category = "Combat Events") + FOnHealthChanged OnHealthChanged; + + UPROPERTY(BlueprintAssignable, Category = "Combat Events") + FOnCharacterDied OnCharacterDied; + + UPROPERTY(BlueprintAssignable, Category = "Combat Events") + FOnDamageDealt OnDamageDealt; + + UPROPERTY(BlueprintAssignable, Category = "Combat Events") + FOnDamageReceived OnDamageReceived; + + // Combat Configuration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Settings") + bool bCanTakeDamage = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Settings") + bool bIsAlive = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Settings") + float BaseDamage = 10.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Settings") + float AttackRange = 200.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Settings") + float AttackCooldown = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Settings") + float BlockDamageReduction = 0.5f; + + // Combat State + UPROPERTY(BlueprintReadOnly, Category = "Combat State") + bool bIsAttacking = false; + + UPROPERTY(BlueprintReadOnly, Category = "Combat State") + bool bIsBlocking = false; + + UPROPERTY(BlueprintReadOnly, Category = "Combat State") + bool bIsDodging = false; + + UPROPERTY(BlueprintReadOnly, Category = "Combat State") + float LastAttackTime = 0.0f; + + // Damage Types + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Types") + FGameplayTag PhysicalDamageTag; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Types") + FGameplayTag MagicalDamageTag; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Types") + FGameplayTag TrueDamageTag; + + // Combat Functions + UFUNCTION(BlueprintCallable, Category = "Combat") + bool CanAttack() const; + + UFUNCTION(BlueprintCallable, Category = "Combat") + bool CanBlock() const; + + UFUNCTION(BlueprintCallable, Category = "Combat") + bool CanDodge() const; + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StartAttack(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StopAttack(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StartBlock(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StopBlock(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StartDodge(); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void StopDodge(); + + // Damage Functions + UFUNCTION(BlueprintCallable, Category = "Combat") + void DealDamage(AActor* Target, float Damage, FGameplayTag DamageType); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void TakeDamage(AActor* Source, float Damage, FGameplayTag DamageType); + + // Convenience functions with default damage type + UFUNCTION(BlueprintCallable, Category = "Combat") + void DealPhysicalDamage(AActor* Target, float Damage); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void TakePhysicalDamage(AActor* Source, float Damage); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void Heal(float HealAmount); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void Die(AActor* Killer = nullptr); + + UFUNCTION(BlueprintCallable, Category = "Combat") + void Revive(float HealthPercentage = 1.0f); + + // Health Management + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + float GetCurrentHealth() const; + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + float GetMaxHealth() const; + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + float GetHealthPercentage() const; + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + bool IsAlive() const; + + // Combat Stats + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + float GetAttackDamage() const; + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + float GetAttackSpeed() const; + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Combat") + float GetDefense() const; + + // Utility Functions + UFUNCTION(BlueprintCallable, Category = "Combat") + bool IsInRange(AActor* Target) const; + + UFUNCTION(BlueprintCallable, Category = "Combat") + AActor* FindNearestEnemy(float SearchRadius = 1000.0f) const; + + UFUNCTION(BlueprintCallable, Category = "Combat") + TArray FindEnemiesInRange(float SearchRadius) const; + +private: + // Internal References + UPROPERTY() + UAbilitySystemComponent* AbilitySystemComponent; + + UPROPERTY() + UValorAttributeSet* AttributeSet; + + // Internal Functions + void InitializeReferences(); + void UpdateHealthDisplay(); + void CheckDeath(); + float CalculateDamage(float BaseDamage, FGameplayTag DamageType) const; + float CalculateDefense(FGameplayTag DamageType) const; + bool IsEnemy(AActor* Target) const; + + // Combat Timers + FTimerHandle AttackCooldownTimer; + FTimerHandle BlockTimer; + FTimerHandle DodgeTimer; +}; \ No newline at end of file diff --git a/Source/ValorAbilitySystem/Public/ValorCombatData.h b/Source/ValorAbilitySystem/Public/ValorCombatData.h new file mode 100644 index 0000000..17239ac --- /dev/null +++ b/Source/ValorAbilitySystem/Public/ValorCombatData.h @@ -0,0 +1,202 @@ +// Copyright © John Stratton 2024. All rights reserved. Valor & Blade is a trademark of John Stratton. This game was developed using Unreal® Engine. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. This software product contains Unreal® Engine code, including but not limited to computer code, audio-visual materials, and related documentation ("Unreal® Engine Code"). © 2024, Epic Games, Inc. All rights reserved. Any unauthorized copying, alteration, distribution, transmission, performance, display, or other use of this game or any part thereof is prohibited without the express written consent of John Stratton. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. Unreal® Engine, Copyright 1998 - 2024, Epic Games, Inc. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "GameplayTagContainer.h" +#include "ValorCombatData.generated.h" + +UENUM(BlueprintType) +enum class ECombatState : uint8 +{ + Idle, + Attacking, + Blocking, + Dodging, + Stunned, + Dead +}; + +UENUM(BlueprintType) +enum class EDamageType : uint8 +{ + Physical, + Magical, + TrueDamage, + Fire, + Ice, + Lightning, + Poison, + Holy, + Dark +}; + +UENUM(BlueprintType) +enum class ECombatAction : uint8 +{ + None, + LightAttack, + HeavyAttack, + Block, + Dodge, + Special, + Ultimate +}; + +USTRUCT(BlueprintType) +struct VALORABILITYSYSTEM_API FCombatStats +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float BaseDamage = 10.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float AttackSpeed = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float AttackRange = 200.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float CriticalChance = 0.05f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float CriticalMultiplier = 2.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float Defense = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float BlockReduction = 0.5f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Stats") + float DodgeChance = 0.1f; + + FCombatStats() + { + BaseDamage = 10.0f; + AttackSpeed = 1.0f; + AttackRange = 200.0f; + CriticalChance = 0.05f; + CriticalMultiplier = 2.0f; + Defense = 0.0f; + BlockReduction = 0.5f; + DodgeChance = 0.1f; + } +}; + +USTRUCT(BlueprintType) +struct VALORABILITYSYSTEM_API FDamageInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Info") + float Damage = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Info") + EDamageType DamageType = EDamageType::Physical; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Info") + FGameplayTag DamageTag; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Info") + bool bCanCrit = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Info") + bool bIgnoresDefense = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Info") + bool bIgnoresBlock = false; + + FDamageInfo() + { + Damage = 0.0f; + DamageType = EDamageType::Physical; + DamageTag = FGameplayTag::EmptyTag; + bCanCrit = true; + bIgnoresDefense = false; + bIgnoresBlock = false; + } +}; + +USTRUCT(BlueprintType) +struct VALORABILITYSYSTEM_API FCombatActionData +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + ECombatAction ActionType = ECombatAction::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + FDamageInfo DamageInfo; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + float Duration = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + float Cooldown = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + float StaminaCost = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + float ManaCost = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Action Data") + FGameplayTag RequiredTag; + + FCombatActionData() + { + ActionType = ECombatAction::None; + Duration = 0.0f; + Cooldown = 0.0f; + StaminaCost = 0.0f; + ManaCost = 0.0f; + RequiredTag = FGameplayTag::EmptyTag; + } +}; + +/** + * UValorCombatData + * + * Data asset containing combat configuration and stats for characters. + * Can be used to define different combat profiles for different character types. + */ +UCLASS(BlueprintType) +class VALORABILITYSYSTEM_API UValorCombatData : public UDataAsset +{ + GENERATED_BODY() + +public: + UValorCombatData(); + + // Combat Stats + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Configuration") + FCombatStats CombatStats; + + // Available Combat Actions + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat Actions") + TArray AvailableActions; + + // Damage Type Tags + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage Types") + TMap DamageTypeTags; + + // Combat State Tags + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat States") + TMap CombatStateTags; + + // Utility Functions + UFUNCTION(BlueprintCallable, Category = "Combat Data") + FGameplayTag GetDamageTypeTag(EDamageType DamageType) const; + + UFUNCTION(BlueprintCallable, Category = "Combat Data") + FGameplayTag GetCombatStateTag(ECombatState CombatState) const; + + UFUNCTION(BlueprintCallable, Category = "Combat Data") + FCombatActionData GetActionData(ECombatAction ActionType) const; + + UFUNCTION(BlueprintCallable, Category = "Combat Data") + bool HasAction(ECombatAction ActionType) const; +}; \ No newline at end of file