Skip to content

Commit d860c7d

Browse files
committed
Updated to version 1.0.4. Added attribute group collections. Refactored and optimized the codebase. Implemented a unified attribute registration pipeline.
1 parent 509ffe2 commit d860c7d

8 files changed

Lines changed: 222 additions & 115 deletions

File tree

Plugins/ComponentDynamicAttributes/ComponentDynamicAttributes.uplugin

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"FileVersion": 3,
33
"Version": 1,
4-
"VersionName": "1.0.3",
4+
"VersionName": "1.0.4",
55
"FriendlyName": "Component Dynamic Attributes",
66
"Description": "Actor component for dynamic attribute management in single-player games.Numeric attributes only — nothing extra!",
77
"Category": "Experimental",

Plugins/ComponentDynamicAttributes/Source/ComponentDynamicAttributes/Private/Components/ActorCDA_Init.cpp

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,59 @@
44
#include "ComponentDynamicAttributes.h"
55

66
void UActorCDA::InitializeAttributes()
7+
{
8+
// Rebuild runtime attributes from configured assets/collections.
9+
RebuildAttributesFromAssets();
10+
}
11+
12+
void UActorCDA::AddAttributeDefinition(const FAttributeDefinitionCDA& Definition)
13+
{
14+
if (!Definition.AttributeTag.IsValid())
15+
{
16+
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::AddAttributeDefinition - Invalid GameplayTag."));
17+
return;
18+
}
19+
// Delegate to the unified registration pipeline. Do not manipulate runtime arrays here.
20+
FAttributeRuntimeDataCDA RuntimeData;
21+
RuntimeData.BaseValue = Definition.BaseValue;
22+
RuntimeData.MinValue = Definition.MinValue;
23+
RuntimeData.MaxValue = Definition.MaxValue;
24+
RuntimeData.CachedMaxValue = FMath::Clamp(Definition.BaseValue, Definition.MinValue, Definition.MaxValue);
25+
RuntimeData.CurrentValue = RuntimeData.CachedMaxValue;
26+
RuntimeData.Modifiers.Reset();
27+
RuntimeData.bNeedsRecalculation = false;
28+
29+
if (!RegisterAttributeInternal(Definition.AttributeTag, RuntimeData, Definition, false))
30+
{
31+
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::AddAttributeDefinition - Duplicate GameplayTag found: %s. Skipping."), *Definition.AttributeTag.ToString());
32+
}
33+
}
34+
35+
void UActorCDA::RebuildAttributesFromAssets()
736
{
837
AttributeIndex.Reset();
938
Attributes.Reset();
1039
AttributeDefinitions.Reset();
1140

12-
if (RegisteredAttributeAssets.IsEmpty())
41+
// Count total definitions to reserve arrays
42+
int32 TotalDefinitions = 0;
43+
44+
for (const TObjectPtr<UAttributesCollectionDataAssetCDA>& Collection : RegisteredAttributeCollections)
1345
{
14-
return;
46+
if (!IsValid(Collection))
47+
{
48+
continue;
49+
}
50+
51+
for (const TObjectPtr<UAttributesDataAssetCDA>& Asset : Collection->AttributeAssets)
52+
{
53+
if (IsValid(Asset))
54+
{
55+
TotalDefinitions += Asset->Attributes.Num();
56+
}
57+
}
1558
}
1659

17-
int32 TotalDefinitions = 0;
1860
for (const TObjectPtr<UAttributesDataAssetCDA>& DataAsset : RegisteredAttributeAssets)
1961
{
2062
if (IsValid(DataAsset))
@@ -26,50 +68,66 @@ void UActorCDA::InitializeAttributes()
2668
Attributes.Reserve(TotalDefinitions);
2769
AttributeDefinitions.Reserve(TotalDefinitions);
2870

29-
for (const TObjectPtr<UAttributesDataAssetCDA>& DataAsset : RegisteredAttributeAssets)
71+
// Register collections first
72+
for (const TObjectPtr<UAttributesCollectionDataAssetCDA>& Collection : RegisteredAttributeCollections)
3073
{
31-
if (!IsValid(DataAsset))
74+
if (!IsValid(Collection))
3275
{
33-
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::InitializeAttributes - Invalid AttributesDataAsset."));
3476
continue;
3577
}
3678

37-
if (DataAsset->Attributes.IsEmpty())
79+
RegisterAttributesFromCollection(Collection, false);
80+
}
81+
82+
// Then register single assets
83+
for (const TObjectPtr<UAttributesDataAssetCDA>& DataAsset : RegisteredAttributeAssets)
84+
{
85+
if (!IsValid(DataAsset))
3886
{
87+
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::RebuildAttributesFromAssets - Invalid AttributesDataAsset."));
3988
continue;
4089
}
4190

42-
for (const FAttributeDefinitionCDA& Definition : DataAsset->Attributes)
43-
{
44-
AddAttributeDefinition(Definition);
45-
}
91+
RegisterAttributesFromDataAsset(DataAsset, false);
4692
}
4793
}
4894

49-
void UActorCDA::AddAttributeDefinition(const FAttributeDefinitionCDA& Definition)
95+
void UActorCDA::RegisterAttributesFromDataAsset(UAttributesDataAssetCDA* DataAsset, bool bOverrideExisting)
5096
{
51-
if (!Definition.AttributeTag.IsValid())
97+
if (!IsValid(DataAsset))
5298
{
53-
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::AddAttributeDefinition - Invalid GameplayTag."));
5499
return;
55100
}
56101

57-
if (AttributeIndex.Contains(Definition.AttributeTag))
102+
for (const FAttributeDefinitionCDA& Definition : DataAsset->Attributes)
103+
{
104+
FAttributeRuntimeDataCDA RuntimeData;
105+
106+
RuntimeData.BaseValue = Definition.BaseValue;
107+
RuntimeData.MinValue = Definition.MinValue;
108+
RuntimeData.MaxValue = Definition.MaxValue;
109+
110+
RuntimeData.CachedMaxValue = FMath::Clamp(Definition.BaseValue, Definition.MinValue, Definition.MaxValue);
111+
RuntimeData.CurrentValue = RuntimeData.CachedMaxValue;
112+
113+
RegisterAttributeInternal(Definition.AttributeTag, RuntimeData, Definition, bOverrideExisting);
114+
}
115+
}
116+
117+
void UActorCDA::RegisterAttributesFromCollection(UAttributesCollectionDataAssetCDA* Collection, bool bOverrideExisting)
118+
{
119+
if (!IsValid(Collection))
58120
{
59-
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::AddAttributeDefinition - Duplicate GameplayTag found: %s. Skipping."), *Definition.AttributeTag.ToString());
60121
return;
61122
}
62123

63-
const int32 NewIndex = Attributes.AddDefaulted();
64-
AttributeIndex.Add(Definition.AttributeTag, NewIndex);
65-
AttributeDefinitions.Add(Definition);
124+
for (const TObjectPtr<UAttributesDataAssetCDA>& Asset : Collection->AttributeAssets)
125+
{
126+
if (!IsValid(Asset))
127+
{
128+
continue;
129+
}
66130

67-
FAttributeRuntimeDataCDA& RuntimeData = Attributes[NewIndex];
68-
RuntimeData.BaseValue = Definition.BaseValue;
69-
RuntimeData.MinValue = Definition.MinValue;
70-
RuntimeData.MaxValue = Definition.MaxValue;
71-
RuntimeData.CachedMaxValue = FMath::Clamp(Definition.BaseValue, Definition.MinValue, Definition.MaxValue);
72-
RuntimeData.CurrentValue = RuntimeData.CachedMaxValue;
73-
RuntimeData.Modifiers.Reset();
74-
RuntimeData.bNeedsRecalculation = false;
131+
RegisterAttributesFromDataAsset(Asset.Get(), bOverrideExisting);
132+
}
75133
}

Plugins/ComponentDynamicAttributes/Source/ComponentDynamicAttributes/Private/Components/ActorCDA_Internal.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,61 @@ const FAttributeDefinitionCDA* UActorCDA::GetAttributeDefinition(FGameplayTag Ta
4444
return &AttributeDefinitions[*FoundIndex];
4545
}
4646

47+
bool UActorCDA::RegisterAttributeInternal(
48+
const FGameplayTag& Tag,
49+
const FAttributeRuntimeDataCDA& RuntimeData,
50+
const FAttributeDefinitionCDA& Definition,
51+
bool bOverrideExisting)
52+
{
53+
if (!Tag.IsValid())
54+
{
55+
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::RegisterAttributeInternal - Invalid GameplayTag."));
56+
return false;
57+
}
58+
59+
int32* ExistingIndex = AttributeIndex.Find(Tag);
60+
61+
if (ExistingIndex)
62+
{
63+
if (!bOverrideExisting)
64+
{
65+
return false;
66+
}
67+
68+
Attributes[*ExistingIndex] = RuntimeData;
69+
AttributeDefinitions[*ExistingIndex] = Definition;
70+
71+
FAttributeRuntimeDataCDA& Data = Attributes[*ExistingIndex];
72+
73+
Data.CachedMaxValue = FMath::Clamp(Data.BaseValue, Data.MinValue, Data.MaxValue);
74+
Data.CurrentValue = FMath::Clamp(Data.CurrentValue, Data.MinValue, Data.CachedMaxValue);
75+
76+
if (Data.bNeedsRecalculation)
77+
{
78+
RecalculateAttributeIfNeeded(Tag, *ExistingIndex);
79+
}
80+
81+
return true;
82+
}
83+
84+
const int32 NewIndex = Attributes.Add(RuntimeData);
85+
86+
AttributeDefinitions.Add(Definition);
87+
AttributeIndex.Add(Tag, NewIndex);
88+
89+
FAttributeRuntimeDataCDA& AddedData = Attributes[NewIndex];
90+
91+
AddedData.CachedMaxValue = FMath::Clamp(AddedData.BaseValue, AddedData.MinValue, AddedData.MaxValue);
92+
AddedData.CurrentValue = FMath::Clamp(AddedData.CurrentValue, AddedData.MinValue, AddedData.CachedMaxValue);
93+
94+
if (AddedData.bNeedsRecalculation)
95+
{
96+
RecalculateAttributeIfNeeded(Tag, NewIndex);
97+
}
98+
99+
return true;
100+
}
101+
47102
void UActorCDA::RecalculateAttributeIfNeeded(FGameplayTag Tag, int32 Index)
48103
{
49104
if (!Attributes.IsValidIndex(Index))

Plugins/ComponentDynamicAttributes/Source/ComponentDynamicAttributes/Private/Components/ActorCDA_Modifiers.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,17 @@ void UActorCDA::RemoveModifiersFromSource(FGameplayTag Source)
135135

136136
for (FAttributeRuntimeDataCDA& Data : Attributes)
137137
{
138-
const int32 RemovedCount = Data.Modifiers.RemoveAll([&Source](const FAttributeModifierCDA& Modifier)
138+
bool bRemovedAny = false;
139+
for (int32 ModifierIndex = Data.Modifiers.Num() - 1; ModifierIndex >= 0; --ModifierIndex)
139140
{
140-
return Modifier.Source == Source;
141-
});
141+
if (Data.Modifiers[ModifierIndex].Source == Source)
142+
{
143+
Data.Modifiers.RemoveAt(ModifierIndex);
144+
bRemovedAny = true;
145+
}
146+
}
142147

143-
if (RemovedCount > 0)
148+
if (bRemovedAny)
144149
{
145150
Data.bNeedsRecalculation = true;
146151
}

Plugins/ComponentDynamicAttributes/Source/ComponentDynamicAttributes/Private/Components/ActorCDA_Runtime.cpp

Lines changed: 11 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,74 +27,19 @@ float UActorCDA::GetCurrentValue(FGameplayTag Tag)
2727
return Attributes[Index].CurrentValue;
2828
}
2929

30-
void UActorCDA::RegisterAttribute(FGameplayTag Tag, const FAttributeRuntimeDataCDA& NewAttribute)
30+
void UActorCDA::RegisterAttribute(FName AttributeName, const FAttributeRuntimeDataCDA& NewAttribute)
3131
{
32-
if (!Tag.IsValid())
33-
{
34-
UE_LOG(LogComponentDynamicAttributes, Warning, TEXT("ActorCDA::RegisterAttribute - Invalid GameplayTag: %s"), *Tag.ToString());
35-
return;
36-
}
32+
const FGameplayTag Tag = FGameplayTag::RequestGameplayTag(AttributeName);
3733

38-
int32* FoundIndex = AttributeIndex.Find(Tag);
39-
if (FoundIndex)
40-
{
41-
FAttributeRuntimeDataCDA& Data = Attributes[*FoundIndex];
42-
const float OldValue = Data.CurrentValue;
43-
Data = NewAttribute;
44-
if (Data.CachedMaxValue <= 0.0f)
45-
{
46-
Data.CachedMaxValue = FMath::Clamp(Data.BaseValue, Data.MinValue, Data.MaxValue);
47-
}
48-
else
49-
{
50-
Data.CachedMaxValue = FMath::Clamp(Data.CachedMaxValue, Data.MinValue, Data.MaxValue);
51-
}
52-
Data.CurrentValue = FMath::Clamp(Data.CurrentValue, Data.MinValue, Data.CachedMaxValue);
53-
54-
if (Data.bNeedsRecalculation)
55-
{
56-
RecalculateAttributeIfNeeded(Tag, *FoundIndex);
57-
}
58-
else
59-
{
60-
BroadcastIfValueChanged(Tag, OldValue, Data.CurrentValue);
61-
}
62-
63-
if (AttributeDefinitions.IsValidIndex(*FoundIndex))
64-
{
65-
FAttributeDefinitionCDA& Definition = AttributeDefinitions[*FoundIndex];
66-
Definition.AttributeTag = Tag;
67-
Definition.BaseValue = Data.BaseValue;
68-
Definition.MinValue = Data.MinValue;
69-
Definition.MaxValue = Data.MaxValue;
70-
}
71-
}
72-
else
73-
{
74-
const int32 NewIndex = Attributes.Add(NewAttribute);
75-
AttributeIndex.Add(Tag, NewIndex);
76-
77-
FAttributeDefinitionCDA Definition;
78-
Definition.AttributeTag = Tag;
79-
Definition.BaseValue = NewAttribute.BaseValue;
80-
Definition.MinValue = NewAttribute.MinValue;
81-
Definition.MaxValue = NewAttribute.MaxValue;
82-
AttributeDefinitions.Add(Definition);
83-
84-
FAttributeRuntimeDataCDA& AddedData = Attributes[NewIndex];
85-
AddedData.CachedMaxValue = FMath::Clamp(AddedData.BaseValue, AddedData.MinValue, AddedData.MaxValue);
86-
AddedData.CurrentValue = FMath::Clamp(AddedData.CurrentValue, AddedData.MinValue, AddedData.CachedMaxValue);
87-
88-
if (AddedData.Modifiers.Num() > 0)
89-
{
90-
AddedData.bNeedsRecalculation = true;
91-
}
92-
93-
if (AddedData.bNeedsRecalculation)
94-
{
95-
RecalculateAttributeIfNeeded(Tag, NewIndex);
96-
}
97-
}
34+
// Build a definition using runtime values so RegisterAttributeInternal can keep definitions in sync
35+
FAttributeDefinitionCDA Definition;
36+
Definition.AttributeTag = Tag;
37+
Definition.BaseValue = NewAttribute.BaseValue;
38+
Definition.MinValue = NewAttribute.MinValue;
39+
Definition.MaxValue = NewAttribute.MaxValue;
40+
41+
// Procedural registration should override existing by default (preserve previous behavior)
42+
RegisterAttributeInternal(Tag, NewAttribute, Definition, true);
9843
}
9944

10045
bool UActorCDA::HasAttribute(FGameplayTag Tag) const

0 commit comments

Comments
 (0)