Jittery Movement in client when using the Gameplay Ability System Attributes

Recently i have been trying to learn more about the gameplay ability system and how to implement it in my current project, which is a multiplayer game. I’ve chosen to put the GAS component and the attribute set in the Player State, by it being unique to each player and replicated by default. In my Attribute set all my attributes have been set up properly and flagged to be replicated.

But my main problem is that the movement component seems to not be in sync with the server, as the client shows a jittery movement in its owning character while the server does not.

As far as i tested, my attributes are being replicated correctly, the player does change the speed when affected by an effect. The speed attribute (WalkSpeed and Crouched Walk Speed) changes the movement component speed.

Some code:

Attribute Set (StandardAttributes)

        //header

  public:
	UPROPERTY(BlueprintReadOnly, Category = "Speed", ReplicatedUsing = OnRep_WalkSpeed)
	FGameplayAttributeData WalkSpeed;
	ATTRIBUTE_ACCESSORS(UStandardAttributes, WalkSpeed);

	UPROPERTY(BlueprintReadOnly, Category = "Speed", ReplicatedUsing = OnRep_CrouchedWalkSpeed)
	FGameplayAttributeData CrouchedWalkSpeed;
	ATTRIBUTE_ACCESSORS(UStandardAttributes, CrouchedWalkSpeed);
 
 protected:
	UFUNCTION()
	void OnRep_WalkSpeed(const FGameplayAttributeData& OldSpeedMultiplier);

	UFUNCTION()
	void OnRep_CrouchedWalkSpeed(const FGameplayAttributeData& OldSpeedMultiplier);


   //cpp file
UStandardAttributes::UStandardAttributes()
{
	//Setting base values
	WalkSpeed.SetBaseValue(500.0f);
	WalkSpeed.SetCurrentValue(500.0f);
	CrouchedWalkSpeed.SetBaseValue(250.0f);
	CrouchedWalkSpeed.SetCurrentValue(250.0f);

}

void UStandardAttributes::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME_CONDITION_NOTIFY(UStandardAttributes, WalkSpeed, COND_None, REPNOTIFY_Always);
	DOREPLIFETIME_CONDITION_NOTIFY(UStandardAttributes, CrouchedWalkSpeed, COND_None, REPNOTIFY_Always);
}

void UStandardAttributes::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
	Super::PostAttributeChange(Attribute, OldValue, NewValue);

	const FGameplayAbilityActorInfo* ActorInfo = GetActorInfo();

	if (ActorInfo)
	{
		//handle the attribute changes to affect the component
		if (Attribute == GetWalkSpeedAttribute())
		{
			if (UCharacterMovementComponent* MovementComponent = Cast<UCharacterMovementComponent>(ActorInfo->MovementComponent))
			{
				MovementComponent->MaxWalkSpeed = NewValue;
				//MovementComponent->bIgnoreClientMovementErrorChecksAndCorrection = true;
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Ability Error: Could not get Movement Component"));
			}
		}
		if (Attribute == GetCrouchedWalkSpeedAttribute())
		{
			if (UCharacterMovementComponent* MovementComponent = Cast<UCharacterMovementComponent>(ActorInfo->MovementComponent))
			{
				MovementComponent->MaxWalkSpeedCrouched = NewValue;
			}
		}
	}

}

void UStandardAttributes::OnRep_WalkSpeed(const FGameplayAttributeData& OldWalkSpeed)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UStandardAttributes, WalkSpeed, OldWalkSpeed);
}

void UStandardAttributes::OnRep_CrouchedWalkSpeed(const FGameplayAttributeData& OldCrouchedWalkSpeed)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UStandardAttributes, CrouchedWalkSpeed, OldCrouchedWalkSpeed);
}

It does have the PreAttributeChange() and PostGameplayEffectExecute() but it is only use for value clamping.

PlayerState

//Header
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attributes", replicated, meta = (AllowPrivateAccess = "True"))
UAbilitySystemComponent* AbilitySystemComponent;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attributes", replicated, meta = (AllowPrivateAccess = "True"))
UStandardAttributes* StandardAttributes;

//CPP

AGatosPlayerState::AGatosPlayerState()
{

	/*  Ability System Component   */
	//Create and setup ASC
	AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
	AbilitySystemComponent->SetIsReplicated(true);
	AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);

	//initialize Standard Attributes Set
	StandardAttributes = CreateDefaultSubobject<UStandardAttributes>(TEXT("StandardAttributes"));


	//increase PlayerState Net update frequency

	NetUpdateFrequency = 100.f;

}


void AGatosPlayerState::BeginPlay()
{
	Super::BeginPlay();

	//Bind Functions to handle stats and tags changes to the delegate in the ASC
	if (AbilitySystemComponent && StandardAttributes)
	{
		//Attributes
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(StandardAttributes->GetWalkSpeedAttribute()).AddUObject(this, &AGatosPlayerState::HandleWalkSpeedChanged);

		//Tags
		AbilitySystemComponent->RegisterGameplayTagEvent(FGameplayTag::RequestGameplayTag(FName("State.Status.Passive.Tired")),
			EGameplayTagEventType::NewOrRemoved).AddUObject(this, &AGatosPlayerState::TiredTagReceived);
	}
}

void AGatosPlayerState::InitializeAttributes()
{
	if (AbilitySystemComponent && StandardAttributes)
	{
		//Apply a gameplay effect to initialize the attributes -> LoadObject not working for some reason
		UGameplayEffect* InitEffect = LoadObject<UGameplayEffect>(nullptr, TEXT("D:/Documentos/Unreal Projects/Gatos 5.4/Content/ThirdPerson/Blueprints/GAS/GE_IncreaseHealth.uasset"));
		if (InitEffect)
		{
			FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
			FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(InitEffect->GetClass(), 1, EffectContext);
			AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("ERROR: Could not find InitEffect UAsset!"));
		}
	}
}

void AGatosPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	//ensure that Ability System and attributes are replicated for the client
	DOREPLIFETIME(AGatosPlayerState, AbilitySystemComponent);
	DOREPLIFETIME(AGatosPlayerState, StandardAttributes);

}

I hope i didn’t forget anything, but basically this is my implementation of the GAS to change the speed attribute.

So far i’ve tried to make an RPC Multicast function to change the movement component speed, which is handled by the binded function to the attribute change in the player state. It is called by the player state on the server. But unfortunatelly, it did not work, the client player kept the jittery movement

I also tried to use the bIgnoreClientMovementErrorChecksAndCorrection, but with no effect too.