Current version of Lyra 's OnHealthChanged event will always report instigator as null, could relate to how gameplay effect is processed

I think the cause may be related to static method GetInstigatorFromAttrChangeData() in LyraHealthComponent class can only return nullptr when health is changed by any damage abilities because ChangeData.GEModData is never available which may be caused by when InternalUpdateNumericalAttribute() in FActiveGameplayEffectContainer class is called, it required to have either ModData or CurrentModcallbackData available but CurrentModcallbackData is available only when Damage is the attribute that is processed. When Health is the attribute that is processed, CurrentModcallbackData is null and because ModData will always be null since SetAttributeBaseValue() method is calling InternalUpdateNumericalAttribute() with only nullptr as ModData so under no condition can CallbackData.GEModData being successfully broadcasted.

The issue can be observed by set breakpoints in InternalUpdateNumericalAttribute() method and watch how CallbackData.GEModData can never carry meaningful data. But my c++ experience is really limited so the issue may be lying somewhere else so I apologize if this post cause more confusion than it worth.

1 Like

I believe this is a bug as well. It’s especially apparent when you’re trying to predict attribute changes and the client is left unable to get an context about the attribute change because GEmodData is always nullptr.

My teams usage case was that we wanted wanted to be able predict attribute changes and play a hit reaction based on those changes. We are instead working around this issue by writing logic inside of OnActiveGameplayEffectAddedDelegate for the client and then using GetGameplayAttributeValueChangeDelegate. Theres a bunch of tricky codepaths you need to use because the effects are infinite on client and instant on the server. It could be simplified a ton if the GEModData was populated on the client.

I did send a bug report few weeks ago but it wasn’t accept so I think I will keep the post open for now. It is a pretty obvious issue since Lyra project also has AI behaviour dependent on it so I can only hope Epic is already aware of it. It is also possible that because Epic is going to push Iris as the new standard replication solution that maybe issues like this will no longer be their priority to fix. If that is the case it is also kind understandable. Of course if you would like to send another bug report to raise awareness that can also be cool.

Yea, its a bug and I don’t think they’re going fix it. The response I got was basically that “they don’t do attribute prediction in Fortnite.” I don’t think Iris will fix this, this is just an untested (and really unsupported) code path…

The recommended solution is to override your ability system globals to track the predicted effect that GEModData should provide. This solution is pretty clean and doesn’t require engine changes:

Add your extended class:

#pragma once

#include "AbilitySystemGlobals.h"
#include "TestAbilitySystemGlobals.generated.h"

/**
 * 
 */
UCLASS()
class TESTGAME_API UTestAbilitySystemGlobals : public UAbilitySystemGlobals
{
	GENERATED_BODY()

public:	
	virtual void PushCurrentAppliedGE(const FGameplayEffectSpec* Spec, UAbilitySystemComponent* AbilitySystemComponent) override;
	virtual void SetCurrentAppliedGE(const FGameplayEffectSpec* Spec) override;
	virtual void PopCurrentAppliedGE() override;
	const FGameplayEffectSpec* GetCurrentAppliedGE();

	static UTestAbilitySystemGlobals& GetTestGlobals();

private:
	TArray<struct FGameplayEffectSpec> CurrentGameplayEffectSpecStack;
};

Then the .cpp implementation:

#include "Abilities/TestAbilitySystemGlobals.h"
#include "GameplayEffect.h"

void UTestAbilitySystemGlobals::PushCurrentAppliedGE(const FGameplayEffectSpec* Spec, UAbilitySystemComponent* AbilitySystemComponent)
{
	check(IsInGameThread());
	CurrentGameplayEffectSpecStack.Push(*Spec);
}

void UTestAbilitySystemGlobals::SetCurrentAppliedGE(const FGameplayEffectSpec* Spec)
{
	check(IsInGameThread());
	check(CurrentGameplayEffectSpecStack.Num() > 0);

	FGameplayEffectSpec& CurrentSpec = CurrentGameplayEffectSpecStack.Top();
	CurrentSpec = *Spec;
}

void UTestAbilitySystemGlobals::PopCurrentAppliedGE()
{
	check(IsInGameThread());
	CurrentGameplayEffectSpecStack.Pop();
}

const FGameplayEffectSpec* UTestAbilitySystemGlobals::GetCurrentAppliedGE()
{
	check(IsInGameThread());

	FGameplayEffectSpec* Spec = nullptr;
	if (CurrentGameplayEffectSpecStack.Num() > 0)
	{
		Spec = &CurrentGameplayEffectSpecStack.Top();
	}

	return Spec;
}

UTestAbilitySystemGlobals& UTestAbilitySystemGlobals::GetTestGlobals()
{
	return *CastChecked<UTestAbilitySystemGlobals>(IGameplayAbilitiesModule::Get().GetAbilitySystemGlobals());
}

Inside of your DefaultGame.ini:

[/Script/GameplayAbilities.AbilitySystemGlobals]
+AbilitySystemGlobalsClassName="/Script/PolarisGame.TestAbilitySystemGlobals"

Then, you can very cleanly handle the nullptr GeModData within your delegate function like:

	const FGameplayEffectSpec* Spec = Data.GEModData ? &Data.GEModData->EffectSpec : UTestAbilitySystemGlobals::GetTestGlobals().GetCurrentAppliedGE();
2 Likes

saved my life been stragling with empty instigator for few days :smiling_face_with_three_hearts:

1 Like

Thanks for the detailed fix info!

I linked this solution on the unofficial Lyra Dev Net discord.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.