Use TakeDamage to modify attributes other than health

I want to be able to modify any character stat (health, speed, wisdom, strength, etc.)

I have the following function which works fine for health.

float AFPSCharacter::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser)
{
	// If health is already gone
	if (Health <= 0.f)
	{
		UE_LOG(LogTemp, Warning, TEXT("Health is already gone"));
		return 0.f;
	}

	// Call the base class - this will tell us how much damage to apply
	const float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	if (ActualDamage > 0.f)
	{
		// Reduce health
		Health -= ActualDamage;
		// Handle death
		if (Health <= 0)
		{
			Die(ActualDamage, DamageEvent, EventInstigator, DamageCauser);
		}
	}

	return ActualDamage;
}

I would like to modify this to work for any character attribute.

I have tried a couple things.

My first approach was to create a new function that wasn’t override and have this one call the Super::TakeDamage. Like so. However, for radial damage ApplyRadialDamage calls TakeDamage under the covers. Therefore, I would still need an overriden TakeDamage to control what happens there. Since TakeDamage is overriden I can’t pass additional parameters, such as an enum for the attribute to modify.

void AFPSCharacter::InflictDamage(EFPSStat StatToDamage, float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser)
{
	// If this was a radial damage event
	if (DamageEvent.StaticStruct() == FRadialDamageEvent::StaticStruct())
	{
		// ApplyRadialDamage here
	}
	else
	{
		// Call the base class TakeDamage
		const float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
	}

	switch (StatToDamage)
	{
	case EFPSStat::Health:
		CharacterStats.Health -= ActualDamage;
	}
}

So I scrapped that approach.
My next idea was to override TakeDamage and then piggyback on it’s existing parameters. One of those parameters is a FDamageEvent. So I would create a custom UDamageType and add the attribute to modify enum there. However, the issue with that is FDamageEvent’s constructor takes a UClass parameter like so.

FDamageEvent
(
    TSubclassOf< class UDamageType > InDamageTypeClass
)

Therefore, I can’t specify an attribute enum per TakeDamage call. Since you can only set defaults on UClass and you can’t actually change any properties on them.

So, the only idea I had is to create a ton of UDamageType classes. It’s really ugly and I would rather not. And even that isn’t realistic because what if I want there to be a damage type that sets the player on fire and also lower there wisdom or something?

So does anyone have any ideas?

havent tried it myself, but i would try to created a struct that derives from FDamageEvent and inside use a struct with the info that you need.

FMyDamageEvent: FDamageEvent
{
FDamageParameters params;
}

FDamageParameters
{
AttributeEnum attribute;
int anotherVariable;
}

then pass it to the TakeDamage method
hope it helps

FMyDamageEvent: FDamageEvent {
FDamageParameters params; }

FDamageParameters { AttributeEnum
attribute; int anotherVariable; }

Could you please elaborate on this? It doesn’t seem to work for me, or maybe I’m just doing it wrong.

USTRUCT(BlueprintType)
struct SIBERIA_API FDamageInfo : FDamageEvent
{
	GENERATED_USTRUCT_BODY()

public:

	UPROPERTY()
	float armorMult;

	UPROPERTY()
	float projSpeed;
};

I’m deriving from FDamageEvent, but you can’t turn the FDamageEvent that is passed into the TakeDamage function into something that derives from it, can you?

It has been a while, in which I focused on other things, so I haven’t used UE4 for a while.
I’ll copy parts of my old code, hope it will work for you.
My FSkillDamageEvent is like your FDamageInfo

//this is a type check
if (DamageEvent.IsOfType(FSkillDamageEvent::ClassID))
	{
//this is the actual cast		
auto* const skillDamageEvent = (FSkillDamageEvent*)&DamageEvent;

	//just an example of using the casted type.
		float damageSum = 0.f;
		//iterating through all damage parts
		for (auto& damage : skillDamageEvent->DamageParts)//#TODO: check bishealing
		{
			FDamageIntensity damageIntensity;
			auto damageType = GetDamageTypeParameters(damage, OUT damageIntensity);

			damageSum += actualDamage;
			
			auto damageToReduce = (GetHealth() - actualDamage <= 0 && !CharacterAttributesComponent->bCanDie) ? GetHealth() - 1 : actualDamage;
			UseResource(damage.Resource, damageToReduce);

			//applying the damage and raising the relevant events
			if (damageToReduce != 0.f)
			{
				ReceiveAnyDamage(damageToReduce, damageType, EventInstigator, DamageCauser);
				OnTakeAnyDamage.Broadcast(this, damageToReduce, damageType, EventInstigator, DamageCauser);
				if (EventInstigator != nullptr)
				{
					EventInstigator->InstigatedAnyDamage(damageToReduce, damageType, this, DamageCauser);
				}
			}

		}
1 Like