#pragma once #include "CoreMinimal.h" #include "AttributeSet.h" #include "AbilitySystemComponent.h" #include "HealthAttributeSet.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthDebuffChanged, float, NewHealthDebuff); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath); #define HEALTHATTRIBUTE_ACCESSORS(PropertyName) \ GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UHealthAttributeSet, PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) /** * Health + MaxHealth, replacing UHealthComponent. HealthDebuff no longer exists as its own * attribute - it's GE_HealthDebuff applying a Multiply modifier to MaxHealth, so the "locked" * fraction is just (1 - MaxHealth.CurrentValue / MaxHealth.BaseValue). See GetHealthDebuff() * on AKingshearthLegacyCharacter. */ UCLASS() class KINGSHEARTHLEGACY_API UHealthAttributeSet : public UAttributeSet { GENERATED_BODY() public: UHealthAttributeSet(); UPROPERTY(BlueprintReadOnly, Category = "Health") FGameplayAttributeData Health; HEALTHATTRIBUTE_ACCESSORS(Health) /** Nominal cap: BaseValue is the undebuffed max, CurrentValue is what GE_HealthDebuff (and similar) leave after their modifiers. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health") FGameplayAttributeData MaxHealth; HEALTHATTRIBUTE_ACCESSORS(MaxHealth) /** Unclamped meta attribute: incoming damage lands here via GE_HealthDamage; PostGameplayEffectExecute converts it into a Health decrease and resets it to 0. Never read directly. */ UPROPERTY(BlueprintReadOnly, Category = "Health") FGameplayAttributeData Damage; HEALTHATTRIBUTE_ACCESSORS(Damage) /** Unclamped meta attribute: incoming healing lands here via GE_HealthHealing; PostGameplayEffectExecute converts it into a Health increase and resets it to 0. Never read directly. */ UPROPERTY(BlueprintReadOnly, Category = "Health") FGameplayAttributeData Healing; HEALTHATTRIBUTE_ACCESSORS(Healing) /** Nominal cap, unaffected by debuffs. Feeds the health bar's total width. */ float GetNominalMaxHealth() const { return MaxHealth.GetBaseValue(); } /** 0-1 fraction of GetNominalMaxHealth() currently locked/unusable (curses, status effects, ...). */ float GetHealthDebuff() const { const float NominalMaxHealth = FMath::Max(GetNominalMaxHealth(), KINDA_SMALL_NUMBER); return FMath::Clamp(1.0f - (GetMaxHealth() / NominalMaxHealth), 0.0f, 1.0f); } UPROPERTY(BlueprintAssignable, Category = "Health") FOnHealthChanged OnHealthChanged; UPROPERTY(BlueprintAssignable, Category = "Health") FOnHealthDebuffChanged OnHealthDebuffChanged; UPROPERTY(BlueprintAssignable, Category = "Health") FOnDeath OnDeath; virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override; virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const override; virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override; virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override; private: void ClampHealth(); }; #undef HEALTHATTRIBUTE_ACCESSORS