Hey there.
Welcome back.
So in the last video we made the execution calculation class and we added an empty constructor.
It won't be empty for long as well as overrode the execute implementation function.
This is the heart of the execution calculation.
This is where the calculation is going to be done.
And we already saw that we have access to quite a bit of information already, but we do need to capture
some attributes.
So in this video, we're going to handle capturing the attributes now to make capturing the attributes
a great deal easier for us.
I'm going to use a technique that I've seen done in several projects, such as in the Action-rpg demo
project released by Epic Games, and that technique involved creating a struct to store our attribute
capture definitions and to define them.
Now that phrase attribute capture definition that should sound familiar to you.
As we've captured attributes before using something called an attribute capture definition, haven't
we?
Now, if that doesn't really ring a bell, I'm going to jog your memory by opening a couple of mics.
Really?
We can just open one of them.
I'm going to open mic Maxhealth.
Now check out this mic.
I'm going to move it over here.
To the right.
We have a gameplay effect attribute capture definition called Vigor def.
These attribute capture definitions are how we capture attributes.
I'm going to alt oh right into the CPP file and let's take a look at how we capture the attributes.
Well, notice that for one, we use the constructor to set some parameters on that capture definition
and we add it to this relevant attributes to capture.
This is a array of attribute capture definitions on the class itself.
So that's how we captured attributes.
Here in the MSI, we used a capture definition and in the actual function calculate base magnitude,
we created some evaluation parameters and called this function get captured attribute magnitude, which
required some evaluation parameters.
We pass those in and we got the value of the captured attribute.
So that's how capturing attributes happens in an MSI for a exec calc or an execution calculation.
We're also going to use this type the gameplay effect attribute capture definition, but we're going
to do things a little bit differently.
So as I said in the exec calc, we're going to create a struct to hold all of our capture definitions
and here's how it's going to work.
We're going to create a struct, so I'm going to declare a new struct and I'm going to call this aura
damage statics.
Now statics is in the name for a reason and we'll see why in just a second.
And notice, I'm not prefixing it with an F now.
That's because this is just a raw struct.
We're not going to expose it to blueprint or the reflection system at all.
It's not going to be a use struct.
It doesn't need a generated body macro.
This is just a raw internal struct used right here in the CPP file.
And to emphasize that fact, I'm not going to add that F that's required for a use struct for example.
Now here in aura damage statics, I can create an aura damage statics constructor and just give it a
body here.
So what are we going to do with aura damage statics?
How exactly are we going to use it?
Well, I'm going to make a static variable of this type that has static storage duration.
This means that this damage statics struct will be instantiated once and each time I access it or each
time it's used.
I'd like to access the same one every time.
That's what static is good for.
So I'm going to make a static function to return the one and only static variable.
Now here's how static variables inside of functions work.
I'm going to make a static function that returns a const or a damage statics reference called damage
statics.
Okay, so this static function returns an aura damage statics struct object.
Here in the function I'm going to declare a static or a damage statics called statics.
Okay, so this is creating a De statics object.
It's kind of like when you create an object without using dynamic memory allocation with pointers and
all that good stuff.
We're just creating a variable the old school way.
It's like saying int A or float B or my special struct s, even though my special struct is not a type
that exists, but you get the drift.
We're creating one only.
We're creating a static one.
And when you create a static variable here inside of a static function, then every time that function
is called we get that same object.
It has static storage duration.
It doesn't go away when the function ends, even though it's not dynamically allocated on the heap as
a pointer using the new keyword, it doesn't matter because it's static, it's going to live on.
So every time we call this function over and over again, we're going to return this De statics and
every time we call it, we'll get the same De statics object.
There'll just be one of them.
So anything we put here in aura damage statics, if we call damage statics to access it, then we'll
get the same one every time.
So what are we going to do with damage statics?
Well, we want to declare some new capture definitions and there's a macro for that.
It's declare attribute capture def.
And what do we put in here?
Well, we just put in something like armor.
Now, this is a macro, right?
It doesn't matter that we don't have an armor variable.
We don't have to pass in a variable.
We just have to put some text in here, some tokens and I'm just putting armor.
Now, what is this going to do?
Well, I can right click and I can go to declaration or usages and I can see the macro.
What is it doing?
It's making an F property pointer called P property.
This is macro magic here.
It's taking whatever I put in here, in this case armor and creating a new variable of type F property
pointer called armor property.
It's taking whatever I pass in.
That's why I don't have to pass in a variable per se, because it's not treating it like a variable.
It's treating it like a series of tokens and then creating a new armor property pointer.
And then look at this.
It's creating an F gameplay effect attribute capture definition called armor def.
Look at that.
So we don't have to declare an armor def, right?
We're just using this macro to create an f property and a gameplay attribute capture definition variable
for us.
Notice there's also a define attribute capture def Notice that this does other things.
It takes in four different things and yes, they are single characters, so that makes it a little bit
hard to learn by just looking at the definition, doesn't it?
But look at what it does.
It takes in.
P So in this case, if we passed in armor, then it would replace this part here with armor and it would
be saying armor property equals right?
And it's calling this template function find field checked specifying f property.
And then here it's getting the static class for us.
So that tells us that's is probably a class name that we pass in.
Right.
And then it's calling get member name checked with that class name and that P that in this hypothetical
situation is armor.
Now it's doing get member name checked which does something right Well it gets a member name and and
checked indicates an assertion of some kind right so after getting that property it also takes armor
def the capture definition and here look we have F gameplay effect attribute capture definition.
This looks like a constructor, right?
So it's constructing a new one of these and it's passing in armor property, right?
It's using this P hash hash, macro magic.
It's just replacing this here.
It's actually concatenating it into one single identifier is what it's doing.
And then look, we have an E gameplay effect attribute capture source T, So it's calling this constructor
creating a new one of these.
Initializing it with this enum.
So presumably T has to be an enum constant on this enum type.
Right?
And then look, we have B, so there's this mysterious B that goes into gameplay effect attribute capture
definition.
I'll just spoil it for you here B stands for boolean and it's whether or not we want a snapshot.
So that's it really should be called snapshot.
These should be whole words.
They shouldn't just be letters.
But hey, whoever made this macro decided it was fine.
So anyway, these macros exist and we can use them to help us out to make our attribute capture definitions
and to define them.
We're going to be using those.
So right here we use declare attribute capture Def so we know that we're going to have an armor def.
Remember, this macro creates something called armor def right here, replaces that P double hash with
what we passed in there.
So it's going to create an F gameplay effect attribute capture definition called armor def.
It's going to do that for us and I'd like to use that other macro that define here.
Let's go back, define attribute capture def.
I'd like to use that for my armor and I'm going to do that in Aura Damage Statics constructor.
So it's only done once.
We only need it done.
One time.
So I'm going to use define attribute capture Def and I'm going to pass in those four things that we
need Now.
First is that's because it's going to call static class on it.
Now S is going to be the class for our attribute set.
That's you or a attribute set.
Notice rider just goes and includes that there or attribute set dot h.
Now the next input is going to be that P that gets replaced using that P double hash symbol.
So in this case it's going to be armor, not armor def, but armor.
Now next we need the enum.
It's the T part of that macro and it's an E gameplay effect.
Attribute capture source double colon T.
We're passing in the T part of that and look at the name of the enum gameplay effect Attribute capture
Source.
Well, the two options are target and source and this is the armor.
We're capturing the armor and if we're going to do a damage calculation, we want the armor of the target,
not the source.
The target is what's getting damaged and we need to see the armor value.
We don't care about the armor of the attacker.
We care about the armor of the victim so that we can use that value to lower the damage a bit.
So we're going to use Target.
And finally, the fourth and final input is that be there.
That's a Boolean.
And this is for whether or not we want to snapshot.
We're going to not snapshot.
So we're going to pass in false.
And there we have it.
We've created and define an attribute capture def called armor and it's stored in damage statics and
we can access it through the damage statics function, the static function that will return that static
variable.
So that creates that capture def.
That's one part of the puzzle there.
But we also have to add this to a list, specifically an array on the calc damage class to tell this
execution calculation class that this capture def should be used to capture that specific attribute.
And we're going to do this in the exec calc constructor, and that array is called Relevant attributes
to capture.
We're going to call add on this array passing in that capture definition that we created with these
macros.
Now remember, declare attribute capture def is going to create an gameplay effect attribute capture
definition called armor def.
That's what it's called.
So if we add here to this array, we can add that armor def, we can access it with the static function
damage statics and from damage statics we can use Dot and look at that.
There's the armor def it's created thanks to that macro.
And now we've successfully added that capture definition to the exit Calcs relevant attributes to capture
array.
This is a similar process to what we did in our MSI, but in the MSI we did it right here after just
setting some properties on our vigor def here.
So this was doing it kind of manually creating the vigor def ourselves, setting properties on it ourselves.
Whereas over here in the exit calc, we're using this macro magic to kind of initialize stuff.
This is a kind of shorthand version of doing what we did in the MSI and in the MSI.
We added that capture Def to relevant attributes to capture.
We're doing that here as well because the exit calc also has relevant attributes to capture.
So a couple of different ways to do the same thing.
Now the question how do we actually capture it here in our execution calculation function execute?
How are we going to actually get that armor definition?
Well, the way to do it is through a function on execution.
Params we're going to take execution params dot and remember this struct has lots of useful functions,
doesn't it?
And some of them say capture in them.
The one I'm interested in is attempt calculate captured attribute magnitude.
Let's call that and see what it requires.
No surprise here it requires an attribute capture definition.
The gameplay effect attribute capture definition.
Well, we know how to get that.
We can use the static damage statics function.
I'm not really going to call it a getter, but it's essentially a getter for our static instance of
this struct which contains the armor def.
So that can satisfy that first input.
Now, next, it requires an aggregator evaluate parameters.
Remember in our MSI, when we had to create one of these, it was called evaluation parameters.
No, I don't either.
Let's go back and look.
And right here in calculate base magnitude, we made one of these an aggregator evaluate parameters,
we called it evaluation parameters, and we also had to set.
Source and target tags on it, which we could get through this effect spec.
We're going to go through that same process here and you should probably know how we're going to do
that.
Look, we have an effect spec right here, so why don't we go through that process?
And if you feel up to a tiny mini challenge, why don't you do it yourself?
Make the evaluation parameters, set the source and target tags and pass it into the function.
Go ahead and pause and do it real quick if you're so inclined.
Okay, so first we need the source and target tags.
I'm going to make a const.
Gameplay tag container reference called source tags and use this gameplay effect spec spec dot captured
source tags dot get aggregated tags.
That's the function that I need.
And yes, of course this function returns a pointer to a gameplay tag container, not a reference.
So that's going to actually be a const pointer.
Let's do the same for the target tag.
So I'm going to call this one target tags and instead of captured source tags, we're going to get captured,
target tags, dot get aggregated tags.
Now that we have these, we can make an aggregator evaluate parameters called evaluation parameters,
and we'll set the source and target tags.
So we have evaluation parameters, dot source tags.
We'll set that equal to source tags and evaluation parameters, dot target tags.
We'll set that equal to target tags.
So now that we have evaluation parameters and it has those tags, let's pass that in as the next input
to this function.
Now, next, it requires a float passed in by reference called out magnitude.
It's an out parameter.
So just like we did in the MSI, we're going to create a local variable, not a const one.
We're going to pass it in.
And then after calling this function, it will now have presumably the value of the captured attribute
in this case armor, right?
So we're going to create a float called armor.
We're going to set it equal to zero and we're going to pass in armor.
So armor here.
And after this function, we should have armor.
Pretty cool.
Now, just like we mentioned in the slide a couple videos ago, if we're clamping armor, for example,
in pre attribute change, I know we're not, but if we were, we'd clamp it again here.
Also, if we wanted to make sure that it's never zero, we can also do that too.
We've seen how to do that.
We would do something like armor equals F math.
Max specifying.
The max function for float and we would get the max between zero point F and armor.
So this would make sure that armor is never a negative value, for example.
And that's it.
Now we have armor.
So one of the last things to know how to do is how do we determine what exactly happens as a result
of this execution calculation?
How do we modify the actual value of an attribute with this execution calculation?
Well, as you probably guessed this out, execution output is related to that.
This is an output parameter itself.
We modify attributes using this out, execution output.
So we're going to take it out, execution output.
And what we can do is add output modifier.
Now this requires the gameplay modifier evaluated data type called an output mod.
So we need to make one of those and we can make one right here in the parentheses or we can make one
outside of it.
It's an F gameplay modifier evaluated data.
We can call this evaluated.
Data and for its constructor we have to initialize it with a couple things.
The first input is an gameplay attribute, so we need the attribute that we'd like to modify.
Now, here's the cool thing.
If we're using these macros up here, then we can take our damage statics dot and look at this.
We have armor property, it's an F property pointer.
We can pass that in and that is going to satisfy that gameplay attribute.
Pretty cool, right?
Now the next input is a enum in a enum as byte, but we can pass in an enum of type e gameplay mod op.
So let's type e gameplay mod op.
And this is the modifier operation.
If we hit a double colon, we can see the different types of operations.
So whatever we calculate up here, if we decide that we'd like to modify the armor attribute, well,
we can choose a mod op.
I'm going to choose additive.
Why not?
And then we pass in a magnitude that's a float so we can pass in some value like armor.
Now all we're doing here is taking the value of armor and passing it into this evaluated data.
So we're not actually changing its value unless it was negative, then we'd be making it zero.
But just to prove to ourselves that it's working, we can do plus plus armor.
So we're adding one to armor.
That's our very complicated calculation that we're doing.
And then we have an evaluated data which we can pass in to add output modifier so we can pass in evaluated
data like so.
And this is how we modify the armor attribute.
But the cool thing is we can modify multiple attributes.
We can add output modifier with another evaluated data using a different attribute that we've performed
some other calculation for.
So really we're not limited to just one, but we can just use one.
Okay, I'm going to make that evaluated data const.
There's no point in having it.
Non-const And now we've made a custom calculation designed to capture the armor from the target.
And look at that.
We're adding to the target.
You're welcome.
Right.
We're adding to the armor on the target so we can test this out by using this custom calculation class.
I know it's kind of silly.
All we're doing is adding armor, but we do plan on using this for our calculation regarding combat
damage.
So we'll get to that.
But now we have all of the basic backbone of this thing and we know how to actually modify attributes
using it.
Let's go ahead and run in debug mode.
All right.
So we're back here in the editor and I'm going to use this G damage gameplay effect and scrolling down
in G damage, we see that we have one modifier collapsing.
That dropdown or modifiers has index zero and we're familiar with this.
This is modifying attribute incoming damage with an add modifier op.
It's set by caller specifying the gameplay tag damage.
So I'm going to collapse that.
And just under modifiers, look at this.
We have executions.
I'm going to click plus to add an execution to this gameplay effect and expand that.
And as soon as I expand the index we see calculation class.
So here's where we can specify execution calculation classes.
Now there's also a conditional gameplay effects.
We're not going to touch that just yet, but for calculation class, we can expand the dropdown.
And here's our execution calculation.
Exec Calc damage.
Now that I've selected that exec, calc damage should have its execute implementation function called
when this gameplay effect is applied.
Why don't we test that out by sticking a breakpoint here in execute implementation?
I'm going to go right down to the bottom there and we'll go ahead and launch a fireball at one of our
enemies here.
And there we go.
We hit the break point and we can hover over some of these properties.
We can even show the menu down here to the right as well if we want to expand some things.
But we have lots of data here.
I'm hovering over armor and I see that armor is 11.25.
Don't let this armor equals zero fool you.
We paused way down here and we've already set our armor.
Now this is still a little bit hard to prove to ourselves that we're actually modifying the armor on
the enemy as we're only seeing the value here of armor.
It's 11.25.
But what we could do is we could continue and throw another fireball and see if that armor comes out
to 12.25.
Why don't we do that?
I'm going to click resume.
And I'm going to launch another fireball at the same enemy here.
All right.
So now that we've paused, let's take a look at armor.
And we see that it's now 22.5.
Okay.
And actually, that is correct, because the modifier op is additive, not override.
Right.
So if the armor was 11.25 after we added one to it, that means that after executing this exec calc,
then armor would have had 11.25 added to whatever it was before.
So that should come out to 21.5.
Let's just prove that this is working properly.
Now that we're looking at this from the correct perspective, if armor is 22.5 and we're going to be
adding this using the gameplay mod op additive to the current value of armor and we've added one to
it here, then that means the current value of armor is 21.5, so we'll be adding 22.5 to 21.5 and that
should come out to 44.
So if we resume and we do another fireball, we should see 44 at least before we get to this plus plus.
So if we pause right there before the plus plus, we should see 44.
Let's prove that to ourselves.
Yes, there's 44 and then plus plus armour will make it 45 and then we'll add that to our armour.
So 45 plus 44, that's going to give us 89, right?
So if we resume and we fire yet another fireball, then yes, we see 89.
So that right there proves to us that our execution calculation is being carried out and is actually
modifying the armor attribute.
So kind of a silly modification, but adding one to the armor and then adding that result to the armor
attribute on the target, even though it's not that impressive of an outcome.
The implications of this are massive because we can perform an arbitrarily complex calculation here,
capturing any attributes that we wish and affecting any attributes that we wish.
And that's going to allow us to make a damage calculation that will take all of our primary and secondary
attributes concerned with combat into account.
And we can make some real RPG style damage calculations.
So this is actually really exciting.
So we'll continue with our damage execution calculation in the next video.
I'll see you soon.