34. Listening for Attribute Changes

This commit is contained in:
Edward R. Gonzalez 2024-04-22 01:54:33 -04:00
parent a6dc269630
commit 2695bfc4b6
14 changed files with 376 additions and 201 deletions

Binary file not shown.

Binary file not shown.

View File

@ -8,9 +8,9 @@
UGasaAttributeSet::UGasaAttributeSet() UGasaAttributeSet::UGasaAttributeSet()
{ {
InitHealth( 80.f ); InitHealth( 50.f );
InitMaxHealth( 100.f ); InitMaxHealth( 100.f );
InitMana( 20.f ); InitMana( 25.f );
InitMaxMana( 50.f ); InitMaxMana( 50.f );
} }

View File

@ -14,6 +14,7 @@
#pragma region Engine Forwards #pragma region Engine Forwards
struct FInputActionValue; struct FInputActionValue;
struct FOnAttributeChangeData;
class IAbilitySystemInterface; class IAbilitySystemInterface;
@ -34,6 +35,8 @@ class UCogWindowManager;
// Gasa // Gasa
#pragma region Forwards #pragma region Forwards
struct FWidgetControllerData;
class ACameraMount; class ACameraMount;
class AGasaCharacter; class AGasaCharacter;
class AGasaGameInstance; class AGasaGameInstance;
@ -52,8 +55,6 @@ class UGasaSizeBox;
class UHostWidgetController; class UHostWidgetController;
class UHUDHostWidget; class UHUDHostWidget;
class UWidgetController; class UWidgetController;
struct FWidgetControllerData;
#pragma endregion Forwards #pragma endregion Forwards
#pragma region Logging #pragma region Logging

View File

@ -12,13 +12,18 @@ void FGasaDevOptionsCache::CachedDevOptions()
UGasaDevOptions* DevOpts = GetMutDevOptions(); UGasaDevOptions* DevOpts = GetMutDevOptions();
Template_PlayerCamera = DevOpts->Template_PlayerCamera.LoadSynchronous(); Template_PlayerCamera = DevOpts->Template_PlayerCamera.LoadSynchronous();
ensureMsgf( Template_PlayerCamera != nullptr, TEXT( "Template_PlayerCamera is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++" ) ); ensureMsgf(
Template_PlayerCamera != nullptr, TEXT( "Template_PlayerCamera is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++" )
);
Template_HUD_HostUI = DevOpts->Template_HUD_HostUI.LoadSynchronous(); Template_HUD_HostUI = DevOpts->Template_HUD_HostUI.LoadSynchronous();
ensureMsgf( Template_HUD_HostUI != nullptr, TEXT( "Template_HUD_HostUI is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++" ) ); ensureMsgf( Template_HUD_HostUI != nullptr, TEXT( "Template_HUD_HostUI is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++" ) );
Template_HostWidgetController = DevOpts->Template_HostWidgetController.LoadSynchronous(); Template_HostWidgetController = DevOpts->Template_HostWidgetController.LoadSynchronous();
ensureMsgf( Template_HostWidgetController != nullptr, TEXT( "Template_HostWidgetController is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++" ) ); ensureMsgf(
Template_HostWidgetController != nullptr,
TEXT( "Template_HostWidgetController is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++" )
);
Tag_GlobalPPV = DevOpts->Tag_GlobalPPV; Tag_GlobalPPV = DevOpts->Tag_GlobalPPV;
} }

View File

@ -14,6 +14,7 @@ void AGasaHUD::InitHostWidget(FWidgetControllerData const* WidgetControllerData)
HostWidgetController = NewObject<UHostWidgetController>(this, GetDevOptions()->Template_HostWidgetController.Get()); HostWidgetController = NewObject<UHostWidgetController>(this, GetDevOptions()->Template_HostWidgetController.Get());
HostWidgetController->Data = (* WidgetControllerData); HostWidgetController->Data = (* WidgetControllerData);
HostWidget->SetWidgetController(HostWidgetController); HostWidget->SetWidgetController(HostWidgetController);
HostWidgetController->BindCallbacksToDependencies();
HostWidgetController->BroadcastInitialValues(); HostWidgetController->BroadcastInitialValues();
HostWidget->AddToViewport(); HostWidget->AddToViewport();

View File

@ -1,8 +1,34 @@
#include "HostWidgetController.h" #include "HostWidgetController.h"
#include "AbilitySystem/GasaAbilitySystemComponent_Inlines.h"
#include "AbilitySystem/GasaAttributeSet.h" #include "AbilitySystem/GasaAttributeSet.h"
#include "GameplayEffectTypes.h"
#pragma region Attribute Changed Callbacks
// Attribute Changed Callbacks are generated by GasaGen/GasaGen_HostWidgetController.cpp
void UHostWidgetController::HealthChanged( FOnAttributeChangeData const& Attribute )
{
Event_OnHealthChanged.Broadcast( Attribute.NewValue );
}
void UHostWidgetController::MaxHealthChanged( FOnAttributeChangeData const& Attribute )
{
Event_OnMaxHealthChanged.Broadcast( Attribute.NewValue );
}
void UHostWidgetController::ManaChanged( FOnAttributeChangeData const& Attribute )
{
Event_OnManaChanged.Broadcast( Attribute.NewValue );
}
void UHostWidgetController::MaxManaChanged( FOnAttributeChangeData const& Attribute )
{
Event_OnMaxManaChanged.Broadcast( Attribute.NewValue );
}
#pragma endregion Attribute Changed Callbacks
void UHostWidgetController::BroadcastInitialValues() void UHostWidgetController::BroadcastInitialValues()
{ {
Super::BroadcastInitialValues();
UGasaAttributeSet* GasaAttribs = Cast<UGasaAttributeSet>( Data.Attributes ); UGasaAttributeSet* GasaAttribs = Cast<UGasaAttributeSet>( Data.Attributes );
if ( GasaAttribs ) if ( GasaAttribs )
{ {
@ -12,3 +38,24 @@ void UHostWidgetController::BroadcastInitialValues()
Event_OnMaxManaChanged.Broadcast( GasaAttribs->GetMaxMana() ); Event_OnMaxManaChanged.Broadcast( GasaAttribs->GetMaxMana() );
} }
} }
void UHostWidgetController::BindCallbacksToDependencies()
{
UGasaAbilitySystemComp* AbilitySystem = Cast<UGasaAbilitySystemComp>( Data.AbilitySystem );
UGasaAttributeSet* GasaAttribs = Cast<UGasaAttributeSet>( Data.Attributes );
FOnGameplayAttributeValueChange& HealthAttributeChangedDelegate =
AbilitySystem->GetGameplayAttributeValueChangeDelegate( GasaAttribs->GetHealthAttribute() );
HealthAttributeChangedDelegate.AddUObject( this, &ThisClass::HealthChanged );
FOnGameplayAttributeValueChange& MaxHealthAttributeChangedDelegate =
AbilitySystem->GetGameplayAttributeValueChangeDelegate( GasaAttribs->GetMaxHealthAttribute() );
MaxHealthAttributeChangedDelegate.AddUObject( this, &ThisClass::MaxHealthChanged );
FOnGameplayAttributeValueChange& ManaAttributeChangedDelegate = AbilitySystem->GetGameplayAttributeValueChangeDelegate( GasaAttribs->GetManaAttribute() );
ManaAttributeChangedDelegate.AddUObject( this, &ThisClass::ManaChanged );
FOnGameplayAttributeValueChange& MaxManaAttributeChangedDelegate =
AbilitySystem->GetGameplayAttributeValueChangeDelegate( GasaAttribs->GetMaxManaAttribute() );
MaxManaAttributeChangedDelegate.AddUObject( this, &ThisClass::MaxManaChanged );
}

View File

@ -23,9 +23,15 @@ public:
UPROPERTY( BlueprintAssignable, Category = "Attributes" ) UPROPERTY( BlueprintAssignable, Category = "Attributes" )
FAttributeFloatChangedSig Event_OnMaxManaChanged; FAttributeFloatChangedSig Event_OnMaxManaChanged;
void HealthChanged( FOnAttributeChangeData const& Data );
void MaxHealthChanged( FOnAttributeChangeData const& Data );
void ManaChanged( FOnAttributeChangeData const& Data );
void MaxManaChanged( FOnAttributeChangeData const& Data );
#pragma endregion Attribute Events #pragma endregion Attribute Events
#pragma region WidgetController #pragma region WidgetController
void BroadcastInitialValues() override; void BroadcastInitialValues() override;
void BindCallbacksToDependencies() override;
#pragma endregion WidgetController #pragma endregion WidgetController
}; };

View File

@ -45,4 +45,5 @@ public:
UFUNCTION() UFUNCTION()
virtual void BroadcastInitialValues() {}; virtual void BroadcastInitialValues() {};
virtual void BindCallbacksToDependencies() {};
}; };

View File

@ -19,196 +19,6 @@ using namespace gen;
#include "GasaGen_DevOptionsCache.cpp" #include "GasaGen_DevOptionsCache.cpp"
#include "GasaGen_HostWidgetController.cpp" #include "GasaGen_HostWidgetController.cpp"
void gen_UHostWidgetController()
{
Array<StringCached> attribute_fields = get_gasa_attribute_fields();
CodeBody ori_HostWidgetController_header = parse_file(path_gasa_ui "HostWidgetController.h");
{
CodeBody header_body = def_body(ECode::Global_Body);
StrC str_UHostWidgetController = txt("UHostWidgetController");
CodeClass ori_UHostWidgetController = NoCode;
Code file_code = ori_HostWidgetController_header.begin();
for ( ; file_code != ori_HostWidgetController_header.end(); ++ file_code )
{
if (s32 never_enter = 0; never_enter)
found: break;
switch (file_code->Type)
{
default:
header_body.append(file_code);
continue;
case ECode::Class:
if ( ! file_code->Name.starts_with(str_UHostWidgetController))
continue;
ori_UHostWidgetController = file_code.cast<CodeClass>();
++ file_code;
goto found;
case ECode::Preprocess_Include:
header_body.append(file_code);
if ( file_code->Content.starts_with(txt("HostWidgetController.generated.h")))
{
header_body.append(fmt_newline);
header_body.append(fmt_newline);
}
continue;
case ECode::Untyped:
header_body.append(file_code);
if (file_code->Content.starts_with( txt("DECLARE_"))
|| file_code->Content.starts_with( txt("UCLASS"))
)
header_body.append(fmt_newline);
continue;
}
}
CodeBody attribute_events = def_body(ECode::Class_Body);
{
attribute_events.append( def_comment( txt("Attribute Events are generated by GasaGen/GasaGen_HostWidgetController.cpp")));
attribute_events.append(fmt_newline);
for ( s32 id = 0; id < attribute_fields.num(); )
{
StringCached attribute_field = attribute_fields[id];
attribute_events.append( code_str(
UPROPERTY(BlueprintAssignable, Category = "Attributes")
));
attribute_events.append(fmt_newline);
attribute_events.append( parse_variable(
token_fmt( "field", (StrC) attribute_field, stringize( FAttributeFloatChangedSig Event_On<field>Changed; ))
));
++ id;
if ( id < attribute_fields.num() )
{
attribute_events.append(fmt_newline);
}
}
}
CodeClass new_UHostWidgetController = ori_UHostWidgetController.duplicate().cast<CodeClass>();
CodeBody new_body = def_body(ECode::Class_Body);
for (Code code = ori_UHostWidgetController->Body.begin();
code != ori_UHostWidgetController->Body.end();
++ code )
{
switch (code->Type)
{
default:
new_body.append(code);
continue;
case ECode::Preprocess_Pragma:
{
local_persist bool found = false;
if (found)
{
new_body.append(code);
continue;
}
CodePragma pragma = code.cast<CodePragma>();
if ( pragma->Content.starts_with(txt("region Attribute Events")) )
{
new_body.append(pragma);
++ code;
new_body.append(attribute_events);
while (code->Type != ECode::Preprocess_Pragma
|| ! code->Content.starts_with(txt("endregion Attribute Events")))
++ code;
new_body.append( code );
found = true;
}
}
break;
case ECode::Untyped:
new_body.append(code);
if (code->Content.starts_with( txt("GENERATED_BODY")))
new_body.append(fmt_newline);
}
}
new_body.append(fmt_newline);
new_UHostWidgetController->Body = new_body;
header_body.append(new_UHostWidgetController);
for (; file_code != ori_HostWidgetController_header.end(); ++ file_code)
{
header_body.append(file_code);
}
Builder header = Builder::open(path_gasa_ui "HostWidgetController.h");
header.print(header_body);
header.write();
format_file(path_gasa_ui "HostWidgetController.h");
}
CodeBody ori_HostWidgetController_source = parse_file(path_gasa_ui "HostWidgetController.cpp");
{
CodeBody source_body = def_body(ECode::Global_Body);
CodeBody broadcast_calls = def_body(ECode::Function_Body);
for (StringCached field : attribute_fields)
{
broadcast_calls.append( code_fmt( "field", (StrC)field,
stringize( Event_On<field>Changed.Broadcast( GasaAttribs->Get<field>() ); )
));
}
CodeFn BroadcastInitialValues = parse_function( token_fmt( "broadcast_calls", (StrC)broadcast_calls.to_string(),
stringize(
void UHostWidgetController::BroadcastInitialValues()
{
Super::BroadcastInitialValues();
// Thiis function is managed by: GasaGen/GasaGen_HostWidgetController.cpp
UGasaAttributeSet* GasaAttribs = Cast<UGasaAttributeSet>(Data.Attributes);
if (GasaAttribs)
{
<broadcast_calls>
}
})
));
for ( Code code : ori_HostWidgetController_source)
{
switch (code->Type)
{
case ECode::Function:
CodeFn function_def = code.cast<CodeFn>();
if ( String::are_equal(function_def->Name, BroadcastInitialValues->Name)
&& function_def->Params.is_equal(BroadcastInitialValues->Params))
{
code = BroadcastInitialValues;
log_fmt("Swapped: %S", BroadcastInitialValues->Name);
}
break;
}
source_body.append(code);
}
Builder source = Builder::open(path_gasa_ui "HostWidgetController.cpp");
source.print(source_body);
source.write();
format_file(path_gasa_ui "HostWidgetController.cpp");
}
}
int gen_main() int gen_main()
{ {
gen::init(); gen::init();

View File

@ -0,0 +1,304 @@
#if GASA_INTELLISENSE_DIRECTIVES
#pragma once
#define GEN_EXPOSE_BACKEND
#include "gen.hpp"
#include "gen.builder.hpp"
#include "GasaGenCommon.cpp"
#endif
void gen_UHostWidgetController()
{
Array<StringCached> attribute_fields = get_gasa_attribute_fields();
CodeBody ori_HostWidgetController_header = parse_file(path_gasa_ui "HostWidgetController.h");
{
CodeBody header_body = def_body(ECode::Global_Body);
StrC str_UHostWidgetController = txt("UHostWidgetController");
CodeClass ori_UHostWidgetController = NoCode;
Code file_code = ori_HostWidgetController_header.begin();
for ( ; file_code != ori_HostWidgetController_header.end(); ++ file_code )
{
if (s32 never_enter = 0; never_enter)
found: break;
switch (file_code->Type)
{
default:
header_body.append(file_code);
continue;
case ECode::Class:
if ( ! file_code->Name.starts_with(str_UHostWidgetController))
continue;
ori_UHostWidgetController = file_code.cast<CodeClass>();
++ file_code;
goto found;
case ECode::Preprocess_Include:
header_body.append(file_code);
if ( file_code->Content.starts_with(txt("HostWidgetController.generated.h")))
{
header_body.append(fmt_newline);
header_body.append(fmt_newline);
}
continue;
case ECode::Untyped:
header_body.append(file_code);
if (file_code->Content.starts_with( txt("DECLARE_"))
|| file_code->Content.starts_with( txt("UCLASS"))
)
header_body.append(fmt_newline);
continue;
}
}
CodeBody attribute_events = def_body(ECode::Class_Body);
{
attribute_events.append( def_comment( txt("Attribute Events are generated by GasaGen/GasaGen_HostWidgetController.cpp")));
attribute_events.append(fmt_newline);
for ( s32 id = 0; id < attribute_fields.num(); ++id )
{
StringCached attribute_field = attribute_fields[id];
attribute_events.append( code_str(
UPROPERTY(BlueprintAssignable, Category = "Attributes")
));
attribute_events.append(fmt_newline);
attribute_events.append( parse_variable(
token_fmt( "field", (StrC) attribute_field, stringize( FAttributeFloatChangedSig Event_On<field>Changed; ))
));
attribute_events.append(fmt_newline);
}
for ( s32 id = 0; id < attribute_fields.num(); ++id )
{
StringCached attribute_field = attribute_fields[id];
attribute_events.append( parse_function(
token_fmt( "field", (StrC) attribute_field, stringize( void <field>Changed(FOnAttributeChangeData const& Data); ))
));
}
}
CodeClass new_UHostWidgetController = ori_UHostWidgetController.duplicate().cast<CodeClass>();
CodeBody new_body = def_body(ECode::Class_Body);
for (Code code = ori_UHostWidgetController->Body.begin();
code != ori_UHostWidgetController->Body.end();
++ code )
{
switch (code->Type)
{
default:
new_body.append(code);
continue;
case ECode::Preprocess_Pragma:
{
local_persist bool found = false;
if (found)
{
new_body.append(code);
continue;
}
CodePragma pragma = code.cast<CodePragma>();
if ( pragma->Content.starts_with(txt("region Attribute Events")) )
{
new_body.append(pragma);
++ code;
new_body.append(attribute_events);
while (code->Type != ECode::Preprocess_Pragma
|| ! code->Content.starts_with(txt("endregion Attribute Events")))
++ code;
new_body.append( code );
found = true;
}
}
break;
case ECode::Untyped:
new_body.append(code);
if (code->Content.starts_with( txt("GENERATED_BODY")))
new_body.append(fmt_newline);
}
}
new_body.append(fmt_newline);
new_UHostWidgetController->Body = new_body;
header_body.append(new_UHostWidgetController);
for (; file_code != ori_HostWidgetController_header.end(); ++ file_code)
{
header_body.append(file_code);
}
Builder header = Builder::open(path_gasa_ui "HostWidgetController.h");
header.print(header_body);
header.write();
format_file(path_gasa_ui "HostWidgetController.h");
}
CodeBody ori_HostWidgetController_source = parse_file(path_gasa_ui "HostWidgetController.cpp");
{
CodeBody source_body = def_body(ECode::Global_Body);
CodeFn BroadcastInitialValues = NoCode;
{
CodeBody broadcast_calls = def_body(ECode::Function_Body);
for (StringCached field : attribute_fields)
{
broadcast_calls.append( code_fmt( "field", (StrC)field,
stringize( Event_On<field>Changed.Broadcast( GasaAttribs->Get<field>() ); )
));
}
BroadcastInitialValues = parse_function( token_fmt(
"broadcast_calls", (StrC)broadcast_calls.to_string(),
"generation_notice", txt("\n// This function is managed by: GenGasa/GenGasa_HostWidgetController.cpp\n\n"),
stringize(
void UHostWidgetController::BroadcastInitialValues()
{
<generation_notice>
UGasaAttributeSet* GasaAttribs = Cast<UGasaAttributeSet>(Data.Attributes);
if (GasaAttribs)
{
<broadcast_calls>
}
BindCallbacksToDependencies();
})
));
}
CodeFn BindCallbacksToDependencies = NoCode;
{
CodeBody bindings = def_body(ECode::Function_Body);
bindings.append(fmt_newline);
bindings.append(fmt_newline);
for (StringCached field : attribute_fields)
{
bindings.append( code_fmt( "field", (StrC)field,
stringize(
FOnGameplayAttributeValueChange& <field>AttributeChangedDelegate = AbilitySystem->GetGameplayAttributeValueChangeDelegate(GasaAttribs->Get<field>Attribute());
<field>AttributeChangedDelegate.AddUObject(this, &ThisClass::<field>Changed);
)));
bindings.append(fmt_newline);
bindings.append(fmt_newline);
}
BindCallbacksToDependencies = parse_function( token_fmt(
"generation_notice", txt("\n// This function is managed by: GenGasa/GenGasa_HostWidgetController.cpp\n\n"),
"bindings", (StrC)bindings.to_string(),
stringize(
void UHostWidgetController::BindCallbacksToDependencies()
{
<generation_notice>
UGasaAbilitySystemComp* AbilitySystem = Cast<UGasaAbilitySystemComp>(Data.AbilitySystem);
UGasaAttributeSet* GasaAttribs = Cast<UGasaAttributeSet>( Data.Attributes );
<bindings>
})
));
}
CodeBody attribute_callbacks = def_body(ECode::Global_Body);
{
attribute_callbacks.append( def_comment(txt("Attribute Changed Callbacks are generated by GasaGen/GasaGen_HostWidgetController.cpp")));
attribute_callbacks.append(fmt_newline);
for ( s32 id = 0; id < attribute_fields.num(); )
{
StringCached attribute_field = attribute_fields[id];
attribute_callbacks.append( parse_function( token_fmt(
"field", (StrC) attribute_field,
stringize(
void UHostWidgetController::<field>Changed(FOnAttributeChangeData const& Attribute)
{
Event_On<field>Changed.Broadcast(Attribute.NewValue);
})
)));
++ id;
if ( id < attribute_fields.num() )
{
attribute_callbacks.append(fmt_newline);
}
}
}
for ( Code code = ori_HostWidgetController_source.begin();
code != ori_HostWidgetController_source.end();
++ code
)
{
switch (code->Type)
{
case ECode::Preprocess_Pragma:
{
local_persist bool found = false;
if (found)
{
source_body.append(code);
++ code;
continue;
}
CodePragma pragma = code.cast<CodePragma>();
if ( pragma->Content.starts_with(txt("region Attribute Changed Callbacks")) )
{
source_body.append(fmt_newline);
source_body.append(pragma);
++ code;
source_body.append(attribute_callbacks);
while (code->Type != ECode::Preprocess_Pragma
|| ! code->Content.starts_with(txt("endregion Attribute Changed Callbacks")))
++ code;
found = true;
}
}
break;
case ECode::Function:
CodeFn function_def = code.cast<CodeFn>();
if ( String::are_equal(function_def->Name, BroadcastInitialValues->Name)
&& function_def->Params.is_equal(BroadcastInitialValues->Params))
{
source_body.append(BroadcastInitialValues);
log_fmt("Swapped: %S\n", BroadcastInitialValues->Name);
continue;
}
else if (String::are_equal(function_def->Name, BindCallbacksToDependencies->Name)
&& function_def->Params.is_equal(BindCallbacksToDependencies->Params))
{
source_body.append(BindCallbacksToDependencies);
log_fmt("Swapped: %S\n", BindCallbacksToDependencies->Name);
continue;
}
break;
}
source_body.append(code);
}
Builder source = Builder::open(path_gasa_ui "HostWidgetController.cpp");
source.print(source_body);
source.write();
format_file(path_gasa_ui "HostWidgetController.cpp");
}
}

View File

@ -79,7 +79,7 @@ BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true BreakStringLiterals: true
ColumnLimit: 240 ColumnLimit: 160
CompactNamespaces: true CompactNamespaces: true