Attribute clamping issues with mixed instant and duration GameplayEffect

I want to implement a shielding effect that has some stacks and each stack completely mitigates a single damage instance. For that I added a UCombatSet::Shield and UCombatSet::MaxShield (in this scenario value of 3) attributes to keep track of the charges left.

I also have two GE:

  • Instant: Adds 1 shield (Add Base)
  • Duration: Gives 2 Shield (Add Base) for 5 seconds

Initially I did the clamping in my CombatSet class:

void UCombatSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	Super::PreAttributeChange(Attribute, NewValue);
	
	if (Attribute == GetShieldAttribute())
	{
		NewValue = FMath::Clamp<float>(NewValue, 0.0f, GetMaxShield());
	}
}

and the damage mitigation in my DamageExecution class:

if (TargetTags->HasTag(TAG_STATUS_BUFF_SHIELDED))
{
	OutExecutionOutput.AddOutputModifier(
		FGameplayModifierEvaluatedData(UCombatSet::GetShieldAttribute(), EGameplayModOp::Additive, -1)
	);
}

This works totally fine when I work with only the instant GE as everything modifies the BaseValue of the attribute. However, there are a couple issues for the Duration GE.

  1. If I already have maxed out my shield and activate the duration GE the value overflows. E.g. BaseValue for Shield is 3 and I activate the GE the CurrentValue is set to 5. So even though in the UI it is capped as max 3 shield, two additional damage instance will be blocked.
    For this case I figure out that I have to implement UCombatSet::PostGameplayEffectExecute as well:
void UCombatSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	Super::PostGameplayEffectExecute(Data);
	
	if (Data.EvaluatedData.Attribute == GetShieldAttribute())
	{
		SetShield(FMath::Clamp(GetShield(), 0.0f, GetMaxShield()));
	}
}
  1. This leads to a new issue now. When I have the duration buff active and receive 1 damage instance, the remaining temporary shield (CurrentValue) will be converted to BaseValue due to the implementation details of how SetShield works. Meaning if the buff expires I’m left with 1 BaseValue instead of 0. For that I tried to set the Shield.CurrentValue instead of the SetShield method in PostGameplayEffectExecute. This worked but lead again to a new issues which seems impossible to resolve.

So my question is: how to I implement such a mechanic? Do I have to use different attributes for the instant Shield and the temporary Shield? Or is there anything I’m completely misunderstanding? I was also thinking about using GE stacks with tag instead of an attribute for shield but couldn’t figure out how to do it properly.