// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "CombatAttacker.h" #include "CombatDamageable.h" #include "Animation/AnimInstance.h" #include "CombatCharacter.generated.h" class USpringArmComponent; class UCameraComponent; class UInputAction; struct FInputActionValue; class UCombatLifeBar; class UWidgetComponent; DECLARE_LOG_CATEGORY_EXTERN(LogCombatCharacter, Log, All); /** * An enhanced Third Person Character with melee combat capabilities: * - Combo attack string * - Press and hold charged attack * - Damage dealing and reaction * - Death * - Respawning */ UCLASS(abstract) class ACombatCharacter : public ACharacter, public ICombatAttacker, public ICombatDamageable { GENERATED_BODY() /** Camera boom positioning the camera behind the character */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) USpringArmComponent* CameraBoom; /** Follow camera */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) UCameraComponent* FollowCamera; /** Life bar widget component */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) UWidgetComponent* LifeBar; protected: /** Jump Input Action */ UPROPERTY(EditAnywhere, Category ="Input") UInputAction* JumpAction; /** Move Input Action */ UPROPERTY(EditAnywhere, Category ="Input") UInputAction* MoveAction; /** Look Input Action */ UPROPERTY(EditAnywhere, Category ="Input") UInputAction* LookAction; /** Mouse Look Input Action */ UPROPERTY(EditAnywhere, Category="Input") UInputAction* MouseLookAction; /** Combo Attack Input Action */ UPROPERTY(EditAnywhere, Category ="Input") UInputAction* ComboAttackAction; /** Charged Attack Input Action */ UPROPERTY(EditAnywhere, Category ="Input") UInputAction* ChargedAttackAction; /** Toggle Camera Side Input Action */ UPROPERTY(EditAnywhere, Category ="Input") UInputAction* ToggleCameraAction; /** Max amount of HP the character will have on respawn */ UPROPERTY(EditAnywhere, Category="Damage", meta = (ClampMin = 0, ClampMax = 100)) float MaxHP = 5.0f; /** Current amount of HP the character has */ UPROPERTY(VisibleAnywhere, Category="Damage") float CurrentHP = 0.0f; /** Life bar widget fill color */ UPROPERTY(EditAnywhere, Category="Damage") FLinearColor LifeBarColor; /** Name of the pelvis bone, for damage ragdoll physics */ UPROPERTY(EditAnywhere, Category="Damage") FName PelvisBoneName; /** Pointer to the life bar widget */ UPROPERTY(EditAnywhere, Category="Damage") TObjectPtr LifeBarWidget; /** Max amount of time that may elapse for a non-combo attack input to not be considered stale */ UPROPERTY(EditAnywhere, Category="Melee Attack", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) float AttackInputCacheTimeTolerance = 1.0f; /** Time at which an attack button was last pressed */ float CachedAttackInputTime = 0.0f; /** If true, the character is currently playing an attack animation */ bool bIsAttacking = false; /** Distance ahead of the character that melee attack sphere collision traces will extend */ UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 500, Units="cm")) float MeleeTraceDistance = 75.0f; /** Radius of the sphere trace for melee attacks */ UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 200, Units = "cm")) float MeleeTraceRadius = 75.0f; /** Distance ahead of the character that enemies will be notified of incoming attacks */ UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 500, Units="cm")) float DangerTraceDistance = 300.0f; /** Radius of the sphere trace to notify enemies of incoming attacks */ UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 200, Units = "cm")) float DangerTraceRadius = 100.0f; /** Amount of damage a melee attack will deal */ UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 100)) float MeleeDamage = 1.0f; /** Amount of knockback impulse a melee attack will apply */ UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm/s")) float MeleeKnockbackImpulse = 250.0f; /** Amount of upwards impulse a melee attack will apply */ UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm/s")) float MeleeLaunchImpulse = 300.0f; /** AnimMontage that will play for combo attacks */ UPROPERTY(EditAnywhere, Category="Melee Attack|Combo") UAnimMontage* ComboAttackMontage; /** Names of the AnimMontage sections that correspond to each stage of the combo attack */ UPROPERTY(EditAnywhere, Category="Melee Attack|Combo") TArray ComboSectionNames; /** Max amount of time that may elapse for a combo attack input to not be considered stale */ UPROPERTY(EditAnywhere, Category="Melee Attack|Combo", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) float ComboInputCacheTimeTolerance = 0.45f; /** Index of the current stage of the melee attack combo */ int32 ComboCount = 0; /** AnimMontage that will play for charged attacks */ UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") UAnimMontage* ChargedAttackMontage; /** Name of the AnimMontage section that corresponds to the charge loop */ UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") FName ChargeLoopSection; /** Name of the AnimMontage section that corresponds to the attack */ UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") FName ChargeAttackSection; /** Flag that determines if the player is currently holding the charged attack input */ bool bIsChargingAttack = false; /** If true, the charged attack hold check has been tested at least once */ bool bHasLoopedChargedAttack = false; /** Camera boom length while the character is dead */ UPROPERTY(EditAnywhere, Category="Camera", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm")) float DeathCameraDistance = 400.0f; /** Camera boom length when the character respawns */ UPROPERTY(EditAnywhere, Category="Camera", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm")) float DefaultCameraDistance = 100.0f; /** Time to wait before respawning the character */ UPROPERTY(EditAnywhere, Category="Respawn", meta = (ClampMin = 0, ClampMax = 10, Units = "s")) float RespawnTime = 3.0f; /** Attack montage ended delegate */ FOnMontageEnded OnAttackMontageEnded; /** Character respawn timer */ FTimerHandle RespawnTimer; /** Copy of the mesh's transform so we can reset it after ragdoll animations */ FTransform MeshStartingTransform; public: /** Constructor */ ACombatCharacter(); protected: /** Called for movement input */ void Move(const FInputActionValue& Value); /** Called for looking input */ void Look(const FInputActionValue& Value); /** Called for combo attack input */ void ComboAttackPressed(); /** Called for combo attack input pressed */ void ChargedAttackPressed(); /** Called for combo attack input released */ void ChargedAttackReleased(); /** Called for toggle camera side input */ void ToggleCamera(); /** BP hook to animate the camera side switch */ UFUNCTION(BlueprintImplementableEvent, Category="Combat") void BP_ToggleCamera(); public: /** Handles move inputs from either controls or UI interfaces */ UFUNCTION(BlueprintCallable, Category="Input") virtual void DoMove(float Right, float Forward); /** Handles look inputs from either controls or UI interfaces */ UFUNCTION(BlueprintCallable, Category="Input") virtual void DoLook(float Yaw, float Pitch); /** Handles combo attack pressed from either controls or UI interfaces */ UFUNCTION(BlueprintCallable, Category="Input") virtual void DoComboAttackStart(); /** Handles combo attack released from either controls or UI interfaces */ UFUNCTION(BlueprintCallable, Category="Input") virtual void DoComboAttackEnd(); /** Handles charged attack pressed from either controls or UI interfaces */ UFUNCTION(BlueprintCallable, Category="Input") virtual void DoChargedAttackStart(); /** Handles charged attack released from either controls or UI interfaces */ UFUNCTION(BlueprintCallable, Category="Input") virtual void DoChargedAttackEnd(); protected: /** Resets the character's current HP to maximum */ void ResetHP(); /** Performs a combo attack */ void ComboAttack(); /** Performs a charged attack */ void ChargedAttack(); /** Called from a delegate when the attack montage ends */ void AttackMontageEnded(UAnimMontage* Montage, bool bInterrupted); public: // ~begin CombatAttacker interface /** Performs the collision check for an attack */ virtual void DoAttackTrace(FName DamageSourceBone) override; /** Performs the combo string check */ virtual void CheckCombo() override; /** Performs the charged attack hold check */ virtual void CheckChargedAttack() override; // ~end CombatAttacker interface // ~begin CombatDamageable interface /** Notifies nearby enemies that an attack is coming so they can react */ void NotifyEnemiesOfIncomingAttack(); /** Handles damage and knockback events */ virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override; /** Handles death events */ virtual void HandleDeath() override; /** Handles healing events */ virtual void ApplyHealing(float Healing, AActor* Healer) override; /** Allows reaction to incoming attacks */ virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override; // ~end CombatDamageable interface /** Called from the respawn timer to destroy and re-create the character */ void RespawnCharacter(); public: /** Overrides the default TakeDamage functionality */ virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; /** Overrides landing to reset damage ragdoll physics */ virtual void Landed(const FHitResult& Hit) override; protected: /** Blueprint handler to play damage dealt effects */ UFUNCTION(BlueprintImplementableEvent, Category="Combat") void DealtDamage(float Damage, const FVector& ImpactPoint); /** Blueprint handler to play damage received effects */ UFUNCTION(BlueprintImplementableEvent, Category="Combat") void ReceivedDamage(float Damage, const FVector& ImpactPoint, const FVector& DamageDirection); protected: /** Initialization */ virtual void BeginPlay() override; /** Cleanup */ virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; /** Handles input bindings */ virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; /** Handles possessed initialization */ virtual void NotifyControllerChanged() override; public: /** Returns CameraBoom subobject **/ FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; } /** Returns FollowCamera subobject **/ FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } };