#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") #pragma push_macro("GET_MEMBER_NAME_CHECKED") #undef UPROPERTY #undef UFUNCTION #undef FORCEINLINE #undef ensure #undef GET_MEMBER_NAME_CHECKED void gen_attribute_set_from_table( UDataTable* table, FString asset_name ) { TMap> AttributesByCategory; { TMap< FName, uint8* > const& RowMap = table->GetRowMap(); for (const TPair& 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& CategoryAttributes = AttributesByCategory.FindOrAdd(CategoryName); CategoryAttributes.Add( * RowData); } } 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>& attributes : AttributesByCategory ) { String category_name = to_string(attributes.Key); CodeComment category_cmt = def_comment(token_fmt("category_name", (StrC)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_, EditAnywhere, BlueprintReadWrite, Category = "Attributes|") FGameplayAttributeData ; )); body.append(field_uproperty); } body.append(fmt_newline); body.append(fmt_newline); } // Generate OnReps for each attribute field for ( TPair< FName, TArray>& 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_(FGameplayAttributeData& Prev); ))); } body.append(fmt_newline); body.append(fmt_newline); body.append(def_pragma(txt("region Getters"))); { // Generate property getters for ( TPair< FName, TArray>& 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 GetAttribute() { static FProperty* Prop = FindFieldChecked(::StaticClass(), GET_MEMBER_NAME_CHECKED(, )); return Prop; } ))); body.append(generated_get_attribute); } body.append(fmt_newline); // Generate value getters for ( TPair< FName, TArray>& attributes : AttributesByCategory ) for (FAttributeSetField attribute : attributes.Value) { body.append(code_fmt("property", (StrC)to_string(attribute.Name), stringize( FORCEINLINE float Get() const { return .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>& attributes : AttributesByCategory ) for (FAttributeSetField attribute : attributes.Value) { body.append(code_fmt("property", (StrC)to_string(attribute.Name), stringize( FORCEINLINE void Set(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&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>& 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 ::Set(float NewVal) { UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent(); if (ensure(AbilityComp)) { AbilityComp->SetNumericAttributeBase(GetAttribute(), 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 const* GetAttributeSet(UAbilitySystemComponent* ASC) { return Cast< >(ASC->GetAttributeSet(::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>& 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 ::Client_OnRep_(FGameplayAttributeData & Prev) { static FProperty* Property = FindFieldChecked(StaticClass(), GET_MEMBER_NAME_CHECKED(, )); GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(Property), , Prev); } ))); 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>& 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()")); 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()")); 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 == GetAttribute()) { NewValue = FMath::Clamp(NewValue, , ); } ))); 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 == GetAttribute()) { Set(FMath::Clamp(Get(), , )); } ))); } 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 ::PreAttributeChange(FGameplayAttribute const& Attribute, float& NewValue) { Super::PreAttributeChange(Attribute, NewValue); } ))); PostGameplayEffectExecute = parse_function(token_fmt( "class_name", (StrC)class_name, "attribute_clamps", (StrC)post_attribute_clamps.to_string(), stringize( void ::PostGameplayEffectExecute(FGameplayEffectModCallbackData const& Data) { Super::PostGameplayEffectExecute(Data); FEffectProperties Props; Props.Populate(Data); } ))); 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>& 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( , ); ))); } GetLifetimeOfReplicatedProps = parse_function(token_fmt( "class_name", (StrC)class_name, "property_lifetimes", (StrC)(field_lifetimes.to_string()), stringize( void ::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); } ))); body.append(GetLifetimeOfReplicatedProps); } source.print(body); } source.write(); format_file(path_source_file); } } void generate_AttributeSets() { // All attribute sets are tracked in Gasa's dev options for this project. TArray< TSoftObjectPtr> AttributeSetTables = Gasa::GetDevOptions()->AttributeSets; check( AttributeSetTables.Num() > 0 ); for ( TSoftObjectPtr 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") #pragma pop_macro("GET_MEMBER_NAME_CHECKED")