// Copyright (c) 2026 Reignance Studios. MIT License, see LICENSE. #include "RSAsyncWatchAttribute.h" #include "AbilitySystemComponent.h" #include "RSAbilitySystemLibrary.h" URSAsyncWatchAttribute* URSAsyncWatchAttribute::WatchAttributes(AActor* Actor, const TArray& Attributes, bool bFireInitialValue) { URSAsyncWatchAttribute* Action = NewObject(); Action->WeakASC = URSAbilitySystemLibrary::FindAbilitySystemComponent(Actor); for (const FGameplayAttribute& Attribute : Attributes) { // A duplicate would bind twice and fire OnChanged twice per change. Action->Watched.AddUnique(Attribute); } Action->bFireInitial = bFireInitialValue; Action->RegisterWithGameInstance(Actor); return Action; } void URSAsyncWatchAttribute::Activate() { UAbilitySystemComponent* ASC = WeakASC.Get(); if (!ASC) { // Common on clients: the PlayerState (and its ASC) has not replicated yet. // Retry from OnRep_PlayerState instead of polling. Cancel(); return; } for (const FGameplayAttribute& Attribute : Watched) { if (!Attribute.IsValid()) { continue; } Bindings.Emplace(Attribute, ASC->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(this, &URSAsyncWatchAttribute::HandleChange)); if (bFireInitial) { const float Value = ASC->GetNumericAttribute(Attribute); OnChanged.Broadcast(Attribute, Value, Value); } } } void URSAsyncWatchAttribute::HandleChange(const FOnAttributeChangeData& Data) { OnChanged.Broadcast(Data.Attribute, Data.NewValue, Data.OldValue); } void URSAsyncWatchAttribute::Cancel() { if (UAbilitySystemComponent* ASC = WeakASC.Get()) { for (const TPair& Binding : Bindings) { ASC->GetGameplayAttributeValueChangeDelegate(Binding.Key).Remove(Binding.Value); } } Bindings.Reset(); Super::Cancel(); }