GASATHON/Project/Source/GasaEditor/GasaGen/AttributeSets.cpp

454 lines
16 KiB
C++
Raw Normal View History

#include "AttributeSets.h"
#include "GasaGen_Common.h"
#include "Gasa/AbilitySystem/GasaAbilitySystem.h"
#include "Gasa/GasaDevOptions.h"
using namespace gen;
#pragma push_macro("UPROPERTY")
#pragma push_macro("UFUNCTION")
#pragma push_macro("FORCEINLINE")
#pragma push_macro("ensure")
2024-10-22 10:25:27 -07:00
#pragma push_macro("GET_MEMBER_NAME_CHECKED")
#undef UPROPERTY
#undef UFUNCTION
#undef FORCEINLINE
#undef ensure
2024-10-22 10:25:27 -07:00
#undef GET_MEMBER_NAME_CHECKED
2024-10-22 10:25:27 -07:00
void gen_attribute_set_from_table( UDataTable* table, FString asset_name )
{
TMap<FName, TArray<FAttributeSetField>> AttributesByCategory;
{
2024-10-22 10:25:27 -07:00
TMap< FName, uint8* > const& RowMap = table->GetRowMap();
for (const TPair<FName, uint8*>& Row : RowMap)
{
FAttributeSetField const* RowData = rcast( FAttributeSetField const* , Row.Value);
// If category is empty, use a default category name
FName CategoryName = RowData->Category.IsNone() ? FName(TEXT("Default")) : RowData->Category;
TArray<FAttributeSetField>& CategoryAttributes = AttributesByCategory.FindOrAdd(CategoryName);
CategoryAttributes.Add( * RowData);
}
}
2024-10-22 10:25:27 -07:00
check( asset_name.StartsWith(TEXT("DT_") ))
asset_name = asset_name.RightChop(3);
String str_AssetName = to_string(asset_name);
String class_name = String::fmt_buf(GlobalAllocator, "U%S", str_AssetName);
String header_file_name = String::fmt_buf(GlobalAllocator, "%S.h", str_AssetName);
String inlines_file_name = String::fmt_buf(GlobalAllocator, "%S_Inlines.h", str_AssetName);
String path_header_file = String::fmt_buf(GlobalAllocator, path_gasa_ability_system "%S.h", str_AssetName);
String path_inlines_file = String::fmt_buf(GlobalAllocator, path_gasa_ability_system "%S_Inlines.h", str_AssetName);
String path_source_file = String::fmt_buf(GlobalAllocator, path_gasa_ability_system "%S.cpp", str_AssetName);
String uht_include_file = String::fmt_buf(GlobalAllocator, "%S.generated.h", str_AssetName);
CodeType type_UAttributeSet = def_type( txt("UAttributeSet") );
CodeComment generation_notice = def_comment(txt("Generated by GasaEditor/GasaGen/GasaGen_AttributeSets.cpp"));
CodeAttributes api_attribute = def_attributes( UModule_GASA_API->Name);
Builder header = builder_open( path_header_file );
{
header.print(generation_notice);
header.print(pragma_once);
header.print(fmt_newline);
CodeInclude Include_AttributeSet = def_include(txt("AttributeSet.h"));
CodeInclude Include_GasaAttributeSet_Generated = def_include(uht_include_file);
header.print( Include_AttributeSet);
header.print( Include_GasaAttributeSet_Generated);
header.print( fmt_newline);
gen::CodeClass attribute_set_class = {};
{
CodeBody body = def_body( CodeT::Class_Body );
{
body.append( UHT_GENERATED_BODY);
body.append( access_public );
body.append( def_constructor() );
body.append(fmt_newline);
// Generate UPROPERTIES for each attribute field, organized by category
for ( TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory )
{
String category_name = to_string(attributes.Key);
CodeComment category_cmt = def_comment(token_fmt("category_name", (StrC)category_name, "<category_name> Attributes"));
body.append(category_cmt);
body.append(fmt_newline);
for (FAttributeSetField attribute : attributes.Value)
{
Code field_uproperty = code_fmt(
"category", (StrC)to_string(attribute.Category),
"property", (StrC)to_string(attribute.Name),
stringize(
UPROPERTY(ReplicatedUsing = Client_OnRep_<property>, EditAnywhere, BlueprintReadWrite, Category = "Attributes|<category>")
FGameplayAttributeData <property>;
));
body.append(field_uproperty);
}
body.append(fmt_newline);
body.append(fmt_newline);
}
// Generate OnReps for each attribute field
for ( TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory )
for ( FAttributeSetField attribute : attributes.Value )
{
Code umeta_UFUNCTION = code_str(UFUNCTION());
body.append(fmt_newline);
body.append(umeta_UFUNCTION);
body.append(fmt_newline);
body.append(code_fmt("property", (StrC)to_string(attribute.Name), stringize(
void Client_OnRep_<property>(FGameplayAttributeData& Prev<property>);
)));
}
body.append(fmt_newline);
body.append(fmt_newline);
body.append(def_pragma(txt("region Getters")));
{
// Generate property getters
for ( TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory )
for (FAttributeSetField attribute : attributes.Value)
{
CodeFn generated_get_attribute = parse_function(token_fmt(
"class_name", (StrC)class_name,
"property", (StrC)to_string(attribute.Name),
stringize(
static FGameplayAttribute Get<property>Attribute()
{
static FProperty* Prop = FindFieldChecked<FProperty>(<class_name>::StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property>));
return Prop;
}
)));
body.append(generated_get_attribute);
}
body.append(fmt_newline);
// Generate value getters
for ( TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory )
for (FAttributeSetField attribute : attributes.Value)
{
body.append(code_fmt("property", (StrC)to_string(attribute.Name),
stringize(
FORCEINLINE float Get<property>() const
{
return <property>.GetCurrentValue();
}
)));
}
body.append(fmt_newline);
}
body.append(def_pragma(txt("endregion Getters")));
body.append(fmt_newline);
body.append(def_pragma(txt("region Setters")));
{
// Generate value setter forwards
for ( TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory )
for (FAttributeSetField attribute : attributes.Value)
{
body.append(code_fmt("property", (StrC)to_string(attribute.Name),
stringize(
FORCEINLINE void Set<property>(float NewVal);
)));
}
}
body.append(def_pragma(txt("endregion Setters")));
body.append(fmt_newline);
body.append(def_pragma(txt("region AttributeSet")));
body.append(code_str(
void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
void PostGameplayEffectExecute(FGameplayEffectModCallbackData const& Data) override;
));
body.append(def_pragma(txt("endregion AttributeSet")));
body.append(fmt_newline);
body.append(def_pragma(txt("region UObject")));
CodeFn GetLifetimeOfReplicatedProps = parse_function(code(
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>&OutLifetimeProps) const override;
));
body.append(GetLifetimeOfReplicatedProps);
body.append(def_pragma(txt("endregion UObject")));
body.append(fmt_newline);
}
attribute_set_class = def_class( class_name, body
, type_UAttributeSet, AccessSpec::Public
, api_attribute
);
header.print(UHT_UCLASS);
header.print(attribute_set_class);
}
header.write();
format_file(path_header_file);
}
Builder inlines = builder_open( path_inlines_file );
{
inlines.print(generation_notice);
inlines.print(pragma_once);
inlines.print(fmt_newline);
inlines.print(def_include(header_file_name));
inlines.print(def_include(txt("AbilitySystemComponent.h")));
inlines.print(fmt_newline);
CodeBody body = def_body(ECode::Global_Body);
{
body.append(def_pragma(txt("region Attribute Setters")));
{
for (TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory)
for (FAttributeSetField attribute : attributes.Value)
{
CodeFn generated_get_attribute = parse_function(token_fmt(
"class_name", (StrC)class_name,
"property", (StrC)to_string(attribute.Name),
stringize(
FORCEINLINE void <class_name>::Set<property>(float NewVal)
{
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
if (ensure(AbilityComp))
{
AbilityComp->SetNumericAttributeBase(Get<property>Attribute(), NewVal);
};
}
)));
body.append(generated_get_attribute);
}
}
body.append(def_pragma(txt("endregion Attribute Setters")));
}
inlines.print(body);
inlines.print(fmt_newline);
CodeNS ns_gasa = parse_namespace(token_fmt(
"class_name", (StrC)class_name,
stringize(
namespace Gasa
{
inline
<class_name> const* GetAttributeSet(UAbilitySystemComponent* ASC)
{
return Cast< <class_name> >(ASC->GetAttributeSet(<class_name>::StaticClass()));
}
}
)));
inlines.print(ns_gasa);
inlines.write();
format_file(path_inlines_file);
}
Builder source = builder_open( path_source_file );
{
source.print(generation_notice);
header.print(fmt_newline);
source.print(def_include(header_file_name));
source.print(def_include(inlines_file_name));
source.print(def_include(txt("EffectProperties.h")));
source.print(fmt_newline);
source.print(def_include(txt("AbilitySystemComponent.h")));
source.print(def_include(txt("Net/UnrealNetwork.h")));
source.print(def_include(txt("Networking/GasaNetLibrary.h")));
source.print(def_include(txt("GameplayEffectExtension.h")));
{
CodeBody body = def_body(CodeT::Global_Body);
body.append(fmt_newline);
CodeConstructor constructor_for_UGasaAttributeSet = parse_constructor(code(
UGasaAttributeSet::UGasaAttributeSet()
{}
));
body.append(constructor_for_UGasaAttributeSet);
// Generate Attribute fields implementation
{
body.append(fmt_newline);
body.append(def_pragma(txt("region Rep Notifies")));
for (TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory)
for (FAttributeSetField attribute : attributes.Value)
{
CodeFn field_impl = parse_function(token_fmt(
"class_name", (StrC)class_name,
"property", (StrC)to_string(attribute.Name),
"from_notice", txt("\n// From GAMEPLAYATTRIBUTE_REPNOTIFY\n"),
stringize(
void <class_name>::Client_OnRep_<property>(FGameplayAttributeData & Prev<property>)
{
<from_notice>
static FProperty* <class_name>Property = FindFieldChecked<FProperty>(StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property>));
GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(<class_name>Property), <property>, Prev<property>);
}
)));
body.append(field_impl);
}
body.append(def_pragma(txt("endregion Rep Notifies")));
body.append(fmt_newline);
}
CodeFn PostGameplayEffectExecute;
CodeFn PreAttributeChange;
{
CodeBody pre_attribute_clamps = def_body(CodeT::Function_Body);
CodeBody post_attribute_clamps = def_body(CodeT::Function_Body);
// Generate field clamping ops for the pre & post functions
{
pre_attribute_clamps.append(fmt_newline);
pre_attribute_clamps.append(fmt_newline);
post_attribute_clamps.append(fmt_newline);
post_attribute_clamps.append(fmt_newline);
for (TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory)
for (FAttributeSetField attribute : attributes.Value)
{
String clamp_min;
if (attribute.bUseMinAttribute)
clamp_min = get_cached_string(token_fmt("MinName", (StrC)to_string(attribute.MinAttribute), "Get<MinName>()"));
else
clamp_min = String::fmt_buf(GlobalAllocator, "%f", attribute.MinValue);
String clamp_max;
if (attribute.bUseMaxAttribute)
clamp_max = get_cached_string(token_fmt("MaxName", (StrC)to_string(attribute.MaxAttribute), "Get<MaxName>()"));
else
clamp_max = String::fmt_buf(GlobalAllocator, "%f", attribute.MaxValue);
pre_attribute_clamps.append(code_fmt(
"field", (StrC)to_string(attribute.Name),
"clamp_min", (StrC)clamp_min,
"clamp_max", (StrC)clamp_max,
stringize(
if (Attribute == Get<field>Attribute())
{
NewValue = FMath::Clamp(NewValue, <clamp_min>, <clamp_max>);
}
)));
post_attribute_clamps.append(code_fmt(
"field", (StrC)to_string(attribute.Name),
"clamp_min", (StrC)clamp_min,
"clamp_max", (StrC)clamp_max,
stringize(
if (Data.EvaluatedData.Attribute == Get<field>Attribute())
{
Set<field>(FMath::Clamp(Get<field>(), <clamp_min>, <clamp_max>));
}
)));
}
pre_attribute_clamps.append(fmt_newline);
pre_attribute_clamps.append(fmt_newline);
post_attribute_clamps.append(fmt_newline);
post_attribute_clamps.append(fmt_newline);
}
PreAttributeChange = parse_function(token_fmt(
"class_name", (StrC)class_name,
"attribute_clamps", (StrC)pre_attribute_clamps.to_string(),
stringize(
void <class_name>::PreAttributeChange(FGameplayAttribute const& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
<attribute_clamps>
}
)));
PostGameplayEffectExecute = parse_function(token_fmt(
"class_name", (StrC)class_name,
"attribute_clamps", (StrC)post_attribute_clamps.to_string(),
stringize(
void <class_name>::PostGameplayEffectExecute(FGameplayEffectModCallbackData const& Data)
{
Super::PostGameplayEffectExecute(Data);
FEffectProperties Props;
Props.Populate(Data);
<attribute_clamps>
}
)));
body.append(PostGameplayEffectExecute);
body.append(fmt_newline);
body.append(PreAttributeChange);
body.append(fmt_newline);
}
CodeFn GetLifetimeOfReplicatedProps;
{
CodeBody field_lifetimes = def_body(CodeT::Function_Body);
field_lifetimes.append(fmt_newline);
field_lifetimes.append(fmt_newline);
for (TPair< FName, TArray<FAttributeSetField>>& attributes : AttributesByCategory)
for (FAttributeSetField attribute : attributes.Value)
{
field_lifetimes.append(code_fmt(
"class_name", (StrC)class_name,
"property", (StrC)to_string(attribute.Name),
stringize(
DOREPLIFETIME_DEFAULT_GAS( <class_name>, <property> );
)));
}
GetLifetimeOfReplicatedProps = parse_function(token_fmt(
"class_name", (StrC)class_name,
"property_lifetimes", (StrC)(field_lifetimes.to_string()),
stringize(
void <class_name>::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
<property_lifetimes>
}
)));
body.append(GetLifetimeOfReplicatedProps);
}
source.print(body);
}
source.write();
format_file(path_source_file);
}
}
2024-10-22 10:25:27 -07:00
void generate_AttributeSets()
{
// All attribute sets are tracked in Gasa's dev options for this project.
TArray< TSoftObjectPtr<UDataTable>> AttributeSetTables = Gasa::GetDevOptions()->AttributeSets;
check( AttributeSetTables.Num() > 0 );
for ( TSoftObjectPtr<UDataTable> table : AttributeSetTables )
{
FGraphEventRef LoadTableTask;
UDataTable* AttributeSetTable = nullptr;
FGraphEventArray Prerequisites;
LoadTableTask = FFunctionGraphTask::CreateAndDispatchWhenReady(
[ & AttributeSetTable, & AttributeSetTables ]()
{
AttributeSetTable = AttributeSetTables[0].LoadSynchronous();
},
TStatId(), &Prerequisites, ENamedThreads::GameThread
);
FTaskGraphInterface::Get().WaitUntilTaskCompletes(LoadTableTask);
gen_attribute_set_from_table( AttributeSetTable, table.GetAssetName() );
}
}
#pragma pop_macro("UPROPERTY")
#pragma pop_macro("UFUNCTION")
#pragma pop_macro("FORCEINLINE")
#pragma pop_macro("ensure")
2024-10-22 10:25:27 -07:00
#pragma pop_macro("GET_MEMBER_NAME_CHECKED")