Welcome back.
Now we've just overridden a function called pre attribute change, and we've used it to perform some
clamping here in the attribute set.
Now pre attribute change is really only good for clamping.
It's not really good for kicking off any logic in response to attribute changes.
But that's not to say that we can't do that.
This is just not the place for it.
Now there is another function that's extremely useful for the attribute set, and we're going to override
that now.
So let's say virtual void and the function is called post gameplay effect execute.
Let's go ahead and get that declared.
And we see that it takes a const reference called data of type gameplay effect mod callback data.
Now I'm going to generate the definition for this function and let that super call exist and now we
have post gameplay effect execute.
Now post gameplay effect execute is pretty self descriptive.
This is executed after a gameplay effect changes an attribute and we have access to a lot of information
via this data input parameter.
We can access a whole lot of info based on the effect that was just applied and we're going to harvest
some information from this data parameter.
And for starters, we're going to take our data and see what attribute has been changed.
We can do that by taking data and accessing evaluated data.
Now you probably see in the pop up effect spec and target.
So already we're seeing some really useful information, but we're going to get that evaluated data
and from evaluated data, if we type the Dot, you'll see that we have some information here, including
attribute the gameplay attribute.
So we know from the data which attribute is being changed here.
For example, we can wrap this in an If statement.
And use comparison and see if the attribute is equal to, for instance, get health attribute.
We'll compare to that attribute, and if it is the health attribute, we can just place a UI log here.
I'll use log temp warning and spit out the value of health.
And we can really just get the current value of health with git health.
Now we can also get the magnitude or the amount changed in the health from that gameplay effect.
I'm going to print that out as well.
So I'm going to copy and paste this line and we're going to say magnitude.
And we can get that from the data as well with data dot evaluated data.
Dot magnitude.
So this will be how much we're changing the health.
Let's just test this out.
I'm going to hit debug and we'll run in debug mode.
I'm running a debug so that I can place breakpoints and inspect some of the data that we have here.
But first, let's just see what we get printed to the output log.
So I'm going to go ahead and press play and walk into the fire volume and we see 49, 48, 47 and magnitude
is negative one.
If we pick up the health potion, we see that we go from 44 to 69.
Magnitude of that gameplay effect was 25.
Here we got our mana increased, which we have nothing printed to the screen because we're only printing
if that attribute is health.
So let's get the health crystal.
And there we see it going up by one Each time you get the gist, we can get information right now.
Let's go ahead and place a break point.
We'll place it right here on the log and let's run the game and trigger that break point.
Okay.
So we've triggered the break point.
We have some information now.
And what I'm mostly interested in right now is data.
And right here at the bottom right, we can expand the data dropdown.
Now data has effects back, evaluated data and target.
Now in just these three variables, data has quite a lot of information.
If we expand effect spec, the effect spec of course has the definition or def we can see the object
in def has the name default G fire area C, so that's the gameplay effect default object.
We have the duration magnitude and all of its information.
We have the period and all of its information.
We can really get any information that we need from this.
Aside from that, we have evaluated data that tells us the attribute.
The modifier operation is additive.
We have that information.
We have the magnitude of negative one.
Here's the actual handle, the active gameplay effect handle.
We have access to that and then we have our target.
The ability system component here is the ability system component of this gameplay ability so we can
access that.
And if we can access the ability system component of the target, that means we can access the ability
actor info, both the owner, actor and the avatar actor.
We can get all that stuff.
So not only can we get information about the target, we also can get information about the source because
the effect spec here is a gameplay effect spec which can get the context and the context has information
about the source if we set that.
Here's the effect context and its data.
We have instigator.
In this case, it's the Or player state.
We have the effect causer BP or a character.
These are just set in the effect context because the effect context was created using the ability system
component of the overlapped actor in this case.
So when you call make effect context from an ability system component, a lot of these are set automatically
and we just happen to be using the ASC of whatever actor is overlapping with our effect.
Actor So in that case, the source is going to be really the ability system component of that actor
because they made the effect context.
But in combat, for example, when Aura launches a fireball at an enemy, she's going to be the source.
And then the enemy that gets hit will be the target.
So ultimately, in post gameplay effect, execute, we have access to just about every entity involved
in this gameplay effect being executed, which is huge.
It's a very powerful thing, so it can be useful for us to collect a bunch of data about the gameplay
effect and store it.
We can create a bunch of local variables here.
We can even store them all in a data structure for this particular effect execution.
And so what we're going to do in this video is collect all the data that we may end up using, and it'll
be a nice learning exercise that will show us just how much data we can get in post gameplay effect,
execute.
And then later in the course, as we're implementing our combat mechanics, we can use this function
as a really handy way to respond to gameplay effects being executed.
So I'm going to go ahead and hit stop.
That's going to close out of the editor and we're going to start collecting some data here.
So I'm going to go ahead and remove the breakpoint and remove this code here.
And we're going to start by getting our effect context.
So if we take data and we get our effect spec from the effect spec, we can call the function get context.
And this returns an gameplay effect context handle.
We're going to store that in a local variable.
So we're going to say gameplay effect context handle and we're going to call this effect context handle.
So that's a piece of information, number one.
Now, how do we get the ability system component from the source of this gameplay effect?
And by the way, when I say source and target, just so we don't get confused, I'm just going to put
a quick comment here that says source equals causer of the effect and then target equals well target
of the effect.
But in parentheses, owner of this attribute set right, the target is the thing being affected.
In other words, this attribute sets owner right.
The source is something else that applied in effect to us and then target is us right in this context.
So we want the ability system component of the source.
Well, we can take this effect context handle and there are many ways to get the ability system component.
One of them is the function get original instigator ability system component and that returns a you
ability system component to be lazy.
I'm going to say auto ASC equals and then hover over auto and then copy the type.
And replace auto with that type.
But ASC isn't good enough.
I'm going to call it source ASC.
So we have the source ability system component.
Now, if we have the source ability system component, we can get lots of things such as the source
actor that owns that ability system component.
Perhaps it's the avatar.
So we can make an A actor called Source Avatar actor, for example, and we can get the source ability
system component and we can always access from the ability system component, the ability actor info
and that has the avatar actor And of course this is a pointer wrapper we have to call, get on that
pointer.
Okay, So we're doing a lot of accessing of pointers here and not all sources may have a valid ability
system component or avatar actor.
So what we're going to do is put some checks in place.
We'll say if source ASC will use is valid here.
And we'll say source ASC ability actor info dot is valid and then we'll also check the avatar actor.
We'll say and source ASC ability actor info.
Avatar actor is valid.
So if you're confused that we're using ability actor info with the dot operator here and then with the
arrow operator here, just remember these are wrapped in pointer wrappers.
T Shared pointer is a smart pointer which has its own utilities, right?
These wrappers are structs that have their own functions and it's the wrappers is valid function we're
calling here.
And after checking to make sure that the pointer is stored in the wrapper is valid, then we can use
the arrow operator on it and check if that avatar actor is valid.
If all this stuff is valid, then we'll store the source avatar actor.
So we'll stick it right in there.
Now we can get other stuff.
For example, the player controller of the source.
If the source has a player controller, right.
That's something we can get from the ability actor info as well.
We can say source, AC ability, actor info, we know that's valid and from that we can get player controller
that's in the ability actor info.
And of course this is in a wrapper, it's in a weak object pointer.
So we'd have to call, get to get that pointer and we can store this.
It's a player controller of type a player controller, so we can make a pointer called source player
controller and we can get that.
Now here's the thing, just in case our ability actor info has a null pointer for the player controller,
we can always fall back on getting the player controller from the actor itself by casting it to a pawn.
So what we'll do here is we'll just check.
We'll say if source player controller is a null pointer in that case and our source avatar actor is
not a null pointer.
Then we'll attempt to get the player controller from the pawn directly.
So we'll perform a cast here.
We'll say if a pawn pawn equals cast to a pawn.
Source, Actor, Source, Avatar, actor.
If that cast is successful, then we now have the pawn We can call get controller using pawn.
Get controller and get controller returns an A controller.
So we could just cast a player controller like this.
Or if we want the controller and don't care if it's a player controller or not, we could just call
this source controller and make it an A controller instead.
We can always cast later if we need it to be a player controller, I'll just call it source controller.
And if we cast source Avatar actor to a pawn, then we don't need to cast.
We'll just say source controller equals pawn get controller.
So you have options here.
Now we're casting because the source may not be a pawn.
The source may not have a controller.
So we're just, you know, setting it if we can, setting it, if it is upon that has a controller and
we can make the pawn const here, we can make the A controller const here, the source ask can be made
const and if we need to change it later, we can change it later.
This can be made const the effect context handle as well.
But either way we know that we can access the controller.
So what we're doing is we're first getting it from the ability actor info, but if ability actor info
contains a null pointer for the source controller, well then we'll just try to get it from the actor
by casting to a pawn and getting the controller then.
So it's kind of a optional second step there.
Now we know that we have the source Avatar actor.
We know that we have the pawn.
We can also access the source character if it is a character.
So, you know, at this point we have a source controller that may or may not be null.
If it's not null, we can get its pawn and store its character as well.
So we can say if source controller is not null or just if source controller, then we can get the source
character, we can say a character source character and we can cast the pawn possessed by the controller
to a character.
We can say cast a character.
And by the way, as I'm doing this, you're going to be thinking in your mind, Can't we do this in
other way?
I can think of three different ways to do it, and the answer is yes.
There are lots of ways you can do all of this stuff.
You can get the possessed pawn.
You can cast the Avatar actor to a character.
There's lots of ways.
So I'm casting to an A character our source controller possessed pawn.
We can get that with get pawn.
Now it says a character is incomplete.
We can always include a character up at the top.
We'll go ahead and include it.
It's in game framework, right?
Game framework, Character dot H.
All right.
So as you can see, we have lots of info here that we got from the source.
Now we can get stuff from the target as well.
We can get the target ability system component, much like we got the source ability system component.
So how are we going to get the target ability system component?
Well, let's go down here to the bottom.
If we take our data notice, we have target here, which is an ability system component.
So there it is and we can easily store that.
We also have the target ability actor info.
So if we want that target actor, we can say a actor target actor and that's going to be data dot,
target, dot, ability, actor info, avatar, actor.
And of course we have to call get on that avatar actor to weak object pointer wrapper.
Now we'll check if these things are valid as well.
We'll say if data dot target dot ability actor info dot is valid and we'll also check data dot target
dot ability actor info Avatar actor.
We'll check if that's valid as well.
Again, we're checking using those pointer wrapper utilities.
If all that stuff's valid, then we'll set a target actor and we can also get the target controller.
We can say a controller target controller equals data, dot target, dot ability, actor info player
controller.
And that's going to be dot get.
Of course we can get the character, a character target character, and we could simply cast to a character
what can we cast?
We can simply cast that target.
Actor Why not?
Now I know that we could access the ability system component directly.
We can also get it through the interface.
We could also get it with you.
Ability system blueprint library, get ability system component.
Right passing in target actor.
And of course I made a typo here.
Let's fix that and we can say you ability system component target ask and we can get it that way.
There are many options here.
The point is that here in post gameplay effect execute from data, we have the ability to get all sorts
of information and we're going to be using this all throughout the course.
So rather than cluttering up post gameplay effect, execute with all of these lines of code, it'd be
handy to refactor a handy little function that can collect all of these into perhaps a struct.
Right.
Maybe we can have some kind of struct called effect properties or something like that and it can store
the source and target ability system components.
It can store the source Avatar.
Actor The Target Avatar actor.
By the way, this should be called Target.
Avatar.
Actor Right.
There's the difference between Avatar and owner actor.
We should specify so we don't confuse ourselves.
So Target, Avatar, actor, we have controller character.
All of these could be stored in some kind of struct and then in post gameplay effect execute we can
fill in that struct with some kind of function for setting the properties based on the effect and that
way we can just retrieve them anytime we want.
And that's kind of the approach that I'd like to take.
So I'd like us to do that here.
So here's your quest for this video.
I'd like you to create the effect properties struct.
You can define this right there in the attribute set header file, call it F effect properties and just
give it some variables, the ability system component for the source and target and the avatar actor,
the controller, the character and go ahead and store the effect context handle in it as well.
And that way we'll create a function together that will fill this in with all the data that we've harvested.
So pause the video and create this struct now.
All right, let's create our effect properties struct.
I'm going to create this in or attribute set.
I'm going to go ahead and just close all other tabs for now.
And right here at the top we'll define our struct.
I'll put it below the attribute accessors.
Macro definition.
I'm going to say struct and this will be called F effect properties and I'd like it to be a use struct.
So I'm going to add the use struct macro for now.
I'm not going to make it blueprint type or anything like that, but I do need the generated body macro
and I'll give it a default constructor that takes no arguments and does nothing.
And first I'm going to have the effect context handle.
So I'm going to make a game play effect context handle called effect context handle.
And after that we'll have a bunch of pointers, right?
And I'm going to make sure to initialize them all to null.
So I'm going to have a new ability system component pointer and I'm going to call it source ask and
initialize it to null pointer and give it a new property and it's just going to be an empty property
and we'll deal with all the source variables first.
So we'll have the source Avatar actor So a actor source Avatar actor equals null pointer Give it a new
property.
We'll have the A controller that's going to be the source controller.
We'll set that equal to null with a new property.
And an a character called source character.
Make that a null pointer as well with you property.
And then we just need these for the target.
So I'm going to copy all of them and paste them just beneath and highlight these four and control.
H And I'm going to change source.
I'm going to search for source and replace it with target and click replace all.
And now we have the target versions.
So quick little shortcut there.
Okay, great.
So that was the challenge.
Now I'd like to make a function for filling in this data, so I'd like it to simply take in an effect
property struct as a non-const reference and we can make this function right here in the attribute set.
I'm going to put it way down in a private section because this is just internal to the attribute set
and I'll make it a void function.
It's going to be called set effect properties and it's going to take a couple of things.
If I alt O or ctrl k o into the CPP file, I'll see that post gameplay effect.
Execute takes a const reference to data and that's where we're getting all this stuff.
So we're going to need to pass that in to our function set effect property.
So I'm going to paste that right there.
And of course we're going to want to pass in an effect properties.
Now this will be non const because we're going to change this struct, we're going to fill it in with
all the data.
So this will be an F effect properties reference called we'll just call it props.
Very important that it's a non const reference and we'll generate the definition and I'm going to cut
this and paste it way up here just above post gameplay effect, execute that way it's right next to
where it's going to be used.
And I'd like to just use all the code we've already done.
I'm going to highlight all of it and cut it out of post gameplay effect, execute and paste it in to
set effect properties.
And now we just need to use all of this work we've done to fill in our props struct.
So let's take a look at those variables in our struct.
Let's first store the effect context handle.
Let's get that stored in our props struct.
We have it right here in a local variable.
Now all we should really have to do is set props dot effect context handle.
So that takes care of the first thing.
Now next we have the source ability system component source as well.
We can set that on props so we can just replace this with props.
Dot source ASC.
Of course we have effect context handle here, but that's now stored on props, so we're going to want
props, dot effect, context handle there.
So we've got our source ability system component.
Now next we're checking if source ASC is valid, but now it's in our struct props source ASC.
So we're going to replace source ASC with props dot source, ASC here, here and here.
Next we're getting the source Avatar actor from Source ASC.
We're going to replace that with props, dot source, ASC and source Avatar Actor is also on props,
so we're going to replace our local variable with props, dot source, Avatar Actor.
We're going to set that on our struct.
We're going to do the same thing for our controller.
We're going to replace this with props, dot source controller and source ASC is going to be props dot
source ASC and we're going to check props dot source controller here and props dot source, Avatar actor.
And then we're going to cast to upon our source Avatar actor on props.
So we're just pasting props dot all over the place.
Same with the source controller here.
We're going to set that there.
And then here we're checking that pointer and then we're replacing source controller with the one on
the props struct.
Okay.
So next we're going to get our variables set for the target.
And first we have Target Avatar actor and that's of course a variable on props.
So we're going to set props, dot target, Avatar actor here and also target controller is going to
be set on props and Target character is going to be set on props and it's casting Target Avatar actor.
We're going to use the one on props and then we have the target.
ASC We're going to set that on props using Target, Avatar, Actor passing that in to get ability system
component.
And now we've set those variables for the target.
So now we have this nice function set effect properties.
And as always, writer is reminding me that this could be a const function and why not?
I'm going to make it a const function because I can real quick just add const to it.
Like just add water, just add const and now we can call set effect properties here in post gameplay
effect execute.
And we have all of our data just packaged up into our props struct.
So what I'd like to do is make an f effect properties struct called props and then pass it into set
effect properties, which takes two things the data which we can pass in and the props which we can
pass in.
And now from within post gameplay effect execute.
We have easy access to all of these properties.
We can get props, dot source, character, source, controller, target, all of these things.
And we just have to keep in mind that depending on what caused the effect, it might not be a character
that caused it, it might not have a controller, it might have some of these properties and not all.
So it's important to check if those are valid before using them.
So we'll be careful.
But now that we have props, we have access to all sorts of things in post gameplay effect, execute
and we'll be using them all throughout this course as we implement more complex mechanics and combat.
So excellent job.
We now have our post gameplay effect execute.
It's going to be very useful for us and I'll see you in the next video.