Why are my gameplay ability attributes behaving like this, someone please help!

Hi,

So I’ve gone through a very helpful piece of documentation by tranek on GitHub and I’ve been following through with some pointers on GAS and I come to clamping max values.


And used the “GetGameplayAttributeValueChangeDelegate” to notify things of changing values. When called from that delegate, or from direct access through the ability system component all acts as expected.

Except when you add and take away health with gameplay effects, they seam to totally ignore the clamping of the value. I don’t understand at all, are the effects cueing up or something, it makes absolutely no sense.

Please help! GAS has been a nightmare to find good documentation for and even worse for troubleshooting

I had to overwrite PreAttributeBaseChange in the attribute set class applying the same clamp. In fact in my test setup it’s the only clamp needed.

/**
	 *	This is called just before any modification happens to an attribute's base value when an attribute aggregator exists.
	 *	This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
	 *	This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
	 *	final value of the attribute actually changing.
	 */
	virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
1 Like

To document a bit.

GameplayEffect.cpp

/** This is the main function that executes a GameplayEffect on Attributes and ActiveGameplayEffects */
void FActiveGameplayEffectsContainer::ExecuteActiveEffectsFrom(FGameplayEffectSpec &Spec, FPredictionKey PredictionKey)
{

        ...

	// ------------------------------------------------------
	//	Modifiers
	//		These will modify the base value of attributes
	// ------------------------------------------------------
	
	bool ModifierSuccessfullyExecuted = false;

	for (int32 ModIdx = 0; ModIdx < SpecToUse.Modifiers.Num(); ++ModIdx)
	{
		const FGameplayModifierInfo& ModDef = SpecToUse.Def->Modifiers[ModIdx];
		
		FGameplayModifierEvaluatedData EvalData(ModDef.Attribute, ModDef.ModifierOp, SpecToUse.GetModifierMagnitude(ModIdx, true));
		ModifierSuccessfullyExecuted |= InternalExecuteMod(SpecToUse, EvalData);

From there you get down to

void FActiveGameplayEffectsContainer::ApplyModToAttribute(const FGameplayAttribute &Attribute, TEnumAsByte<EGameplayModOp::Type> ModifierOp, float ModifierMagnitude, const FGameplayEffectModCallbackData* ModData)
{
	CurrentModcallbackData = ModData;
	float CurrentBase = GetAttributeBaseValue(Attribute);
	float NewBase = FAggregator::StaticExecModOnBaseValue(CurrentBase, ModifierOp, ModifierMagnitude);

	SetAttributeBaseValue(Attribute, NewBase);

	if (CurrentModcallbackData)
	{
		// We expect this to be cleared for us in InternalUpdateNumericalAttribute
		ABILITY_LOG(Warning, TEXT("FActiveGameplayEffectsContainer::ApplyModToAttribute CurrentModcallbackData was not consumed For attribute %s on %s."), *Attribute.GetName(), *Owner->GetFullName());
		CurrentModcallbackData = nullptr;
	}
}

I think the Tranek documentation is misleading on this point.
If you use the PreAttributeChange() you will change the Attribute Current Value (i.e. the temporal one) but not the base value which is more “permanent”:

An Attribute is composed of two values - a BaseValue and a CurrentValue. The BaseValue is the permanent value of the Attribute whereas the CurrentValue is the BaseValue plus temporary modifications from GameplayEffects.

To clamp an attribute change permanently, you want to use:

virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const override;

Note the Base in the function definition.