Welcome back.
In this video, I'd like to make a custom calculation class so that we can have our max health and Max
Mana not only dependent on other attributes, but other variables that are not attributes as well.
So that's going to involve making some changes not only to our gameplay effect.
If we go into default attributes here and open secondary attributes specifically or to secondary attributes,
we have all of these modifiers and we have max health and Max Mana at the bottom.
These have their modifier magnitude calculation type set to attribute based, but I'd like to use custom
calculation class.
So not only are we going to want to change this gameplay effect, but we're also going to want to create
that custom calculation class and that's what we're going to do first.
So to do that, we're going to go into our C plus plus classes folder into Aura public ability system
as this is related to the ability system, we're going to right click and make a new C plus plus class.
But we have to choose all classes and we can find the class we're interested in.
If we type in Mod mag and we see that we have a gameplay mod magnitude calculation, this is that modifier
magnitude calculation or MSI class that we're interested in.
So we're going to choose that class.
And here in ability system, we're going to put this in a sub folder called MSI for modifier magnitude
calculation.
If you don't like the acronym there, we can call it Mod mag Calc if you want to do that MSI.
Either way, we'll go ahead and leave it at Mod Mag Calc and this is going to be the MSI for Max Health,
So I'm just going to call it MSI, underscore Max Health.
So let's go ahead and create this class.
I'm going to go ahead and close the editor and I think I'll close all of my tabs up here so that way
I'll have a clean slate and we can open our public folder open ability system.
Here's our new mod mag calc folder and here's my new MSI called UMC Max Health.
Now there's the you because this is based on the you gameplay mod magnitude calculation class.
So Max health is going to be determined by the calculation we create here.
Now I'm going to go ahead and alt o or control k o to open max health or MSI max health CPP.
So I have both files open here and it's completely blank and empty.
And if this is your first time creating one of these, you have no idea what you're supposed to do,
right?
And that's why we're going to do this together for the first time.
Now, my Mod mag calc needs its own constructor and I'm going to make a public section and put my constructor
in it.
So first things first.
We'll get our constructor declared like so and we'll go ahead and generate the definition for the constructor.
Okay.
That's step one.
So far, so good.
Now how do we actually calculate our custom calculation?
Where do we put it?
Well, we put it in a specific function that we override.
It's going to be a virtual function.
It returns float.
Why?
Because it returns the results that our modifier should produce.
So in this case, all of the calculations that result in this float value we're going to determine here
in this function and it's called calculate base magnitude implementation.
Now I just let Autocomplete fill it out for me here in writer, but it's calculate base magnitude implementation
and it takes an F gameplay effect spec look at that, we get the gameplay effect spec and that's going
to be the effect spec of whatever gameplay effect that has a modifier using this custom calculation.
So we have access to that gameplay effect spec, which means we have access to all the information inside
of that and that's pretty great.
Now mod mag Calcs can capture attributes.
They have access to all the stuff in the effect spec.
Yes, that's why they're so powerful.
But they also have the ability to capture attributes and to capture attributes.
We have to create a variable for the attribute we want to capture.
So we're going to make a private section for that.
And the variable type has to be and get ready because this is a long type.
It's F.
Game play effect, attribute capture definition.
I'm going to go ahead and let Autocomplete fill that out.
Now, this is a gameplay effect attribute capture definition.
It's a struct that defines what attribute we're going to capture.
And I'm going to call this vigor, def def, short for definition.
Okay, So we've declared a new function.
We've made a new variable.
Let's create the definition for this function.
And we don't want to return the super.
We want to return the value that we calculate.
Now if we're going to have this vigor def right now it's just an empty variable.
We have to make sure we capture the vigor attribute and we do that here in the constructor we say vigor.
Def dot attribute to capture.
We specify the attribute to capture and we can access an attribute from or an attribute set with the
static function u or attribute set colon colon get vigor attribute y.
Can we do that because the attribute accessors macro that boilerplate macro.
In case you forgot, I'm just going to show you real quick here in the ability system, here's our attribute
set this one right here, Attribute Accessors.
We know that attribute accessors consists of these macros and one of them is the property getter.
Okay?
This macro results in the creation of get Vigor attribute because we called it with the vigor attribute.
This get vigor attribute is a static function.
That kind of means a lot, right?
It means we don't have to have a pointer to the object, the or attribute set object.
We can just call the static function to get the attribute itself.
That's why we can call it like that here.
Now you or an attribute set is now included here.
Make sure if you don't have fancy tools like writer doing these includes for you that you make sure
to include that of course.
And if we're capturing vigor, we have to define whether we're capturing vigor from the target or the
source.
Now, in our case for this, we're applying the gameplay effect from the aura character to the aura
character.
So they're both the same, but we're going to specify here.
So we're going to say vigor.
Def dot attribute source.
We want to define the attribute source and we use an enum for this e gameplay effect attribute capture
source and our options are source or target.
These are settings that we see in the gameplay effect as well here in C plus plus there are enums,
so we're going to choose target and we also should decide vigor.
Def dot, we should decide whether or not to snapshot.
Now I mentioned that we would talk about Snapshotting later.
We're going to set this to false and Snapshotting has to do with timing.
When do we want to capture the attribute?
When the gameplay effect spec is created or when the gameplay effect spec is applied?
Well, in our case, those two things happen one after the other.
We create the gameplay effects back and we apply it immediately within the same function call, so it
doesn't really matter.
But later we're going to be creating gameplay effect specs and then not necessarily applying them immediately.
For example, we might create an effect spec and set it on a fireball that flies through the air and
then eventually that fireball may or may not hit something and then apply that effects back.
And so the question is if we're capturing attributes in that gameplay effect, do we want to capture
them when that gameplay effect spec was first created at that point in time?
Or do we want to capture them when the effect is applied at that point in time?
Snapshotting is capturing the attribute right away.
As soon as the effect spec is created, not Snapshotting is getting the true value at the time of application
of the effect.
In our case, it doesn't matter.
We're going to use false.
Now after we've set these properties on vigor def then our modifier magnitude calculation needs one
of its variables, which is an array of attributes to capture.
We need to add this vigor def to it and that array is called relevant attributes to capture its a t
array of type f gameplay effect attribute capture definition.
That's the type that our vigor def has.
So we're going to call Add and we're going to add vigor.
Def.
So we've created this VGA def, which is an F gameplay effect.
Attribute capture definition.
We've configured that capture definition and added it to our MKs array of capture definitions called
relevant attributes to capture.
This way, when the modifier is executed, when the effect is applied, then we will be sure to have
VGA captured from the target at the time of application and we can access that here in our calculate
base magnitude implementation function.
Now we have things that we can do here in this function.
We can get any gameplay tags that the source or the target have, and we're not going to use gameplay
tags on the source and target for this MSI, but just in case you may want to do so in the future,
we're going to learn how to do it anyway.
So let's make a comment that says Gather tags from source and target.
They can affect things if you want them to.
So first we make an F gameplay tag container and that's going to be it has to be a pointer and we call
it source tags.
So I made it a const pointer and we can get it from the spec because the gameplay effect spec has the
captured tags from the source and the target we access them with the spec dot captured source tags,
dot get aggregated tags and any tags that that source has.
We now have in this gameplay tag container.
We can do the same thing for target tags.
We can say F gameplay tag container, it's got to be a pointer and we're going to call this target tags
and we can get it from spec dot captured in this case, captured target tags, dot get aggregated tags.
That's how you get the tags.
Now, in order to capture an attribute and get that attributes magnitude its value, we have to create
something called an F aggregator, evaluate parameters.
So we have an F aggregator, evaluate parameters.
We're going to call this evaluation parameters.
And when we create one of these, the evaluation parameters has its own source tags and target tags.
So we pass those on, so we get our source tags and pass those in and we take our evaluation parameters.
Dot target tags, and we also set that using our target tags.
So these are parameters that we have to pass in to a specific function in order to capture the attribute
we're interested in.
In our case, it's vigor and we get that captured value with the function get captured.
Attribute magnitude and this takes the gameplay effect attribute capture definition.
We have that.
That's the vigor def.
So we get that.
We also have to pass in the gameplay effect spec.
Good thing we have that right.
So we pass that in and then it requires those aggregator evaluate parameters.
So that's why we have to pass in our evaluation parameters.
And then finally, how are we going to get that actual value for vigor?
Well, we pass in a float by reference and it's non-const reference.
This function is going to set that float equal to the magnitude or value of vigor at the time it was
captured.
So what we do is we make a local float non-const called vigor.
We initialize to zero because we're good and never have uninitialized variables.
And then we pass in vigor.
And after calling this function, vigor will now have the value of the vigor attribute on the target.
We can always run in debug mode and see if that is the case.
But for now we're going to assume that it is and we're going to take that vigor and usually sometimes
we clamp it so we can take vigor and set it equal to f math and a common trick to make sure that this
is never a negative value is we use Max so we can use Max specifying float, which takes two values
and returns the maximum.
And we would pass in vigor and zero.
And that way, if vigor is less than zero, then we set it to zero.
So that's a common thing that you'll see happen.
So now we have the value of vigor.
That's step one.
But as I mentioned, we want max health to be dependent not only on vigor but also on the player's level.
And that's the whole reason we created the combat interface, because we can always get the source object
of this gameplay effect.
We can get that from the spec, we can say spec dot, get context, dot get source object.
And the source object, as we can see, is a new object.
And as long as this gameplay effect has a source object, we can cast it to that interface.
So in our case, the source object is going to be, well, aura, right?
The Aura character.
So we're going to cast to our interface and we're casting to a combat interface.
And of course the header was auto included.
So there it is there.
And I'm going to create an eye combat interface, local variable called interface.
Actually we'll call it combat interface to be exact.
And then we can have access to the level by saying const int 32 player level equals and we can take
our combat interface and call get player level.
So just like that.
So now that we have our level and our vigor, we decide what this custom calculation returns.
It returns a float here, which means we can have a return statement and we can calculate the value
returned for this modifier calculation however we want.
So the design that I'd like is to have a base value of 80 F plus, and I'd like to have my vigor multiplied
by 2.5.
So I'm going to say 2.5 F times vigor.
And I'd also like to add ten times.
So ten point F times player level.
So now our calculation can be whatever we want it to be.
We could make this as complicated as we want.
We have full freedom here in C plus plus.
What I want to do is just have a base value of 80.
And for every vigor point that we have, we get 2.5.
And for every player level we have, we get ten.
So as the player levels up, maybe they gain some points that they can spend on their attributes and
they upped their vigor.
If they spend one point and up the vigor by one, then their max health will go up by 2.5.
If the player levels up and their level goes up from 1 to 2, then they get ten more added to their
max health.
So that's how I'd like this to behave.
And now we have a custom calculation that we can use in a modifier for max health.
So the next thing to do is to test this out.
So I'm going to run in debug mode and let's see how this works.
Okay, so we're back in the editor.
Let's open our G ora secondary attributes and go down to our modifiers.
And it looks like I have my max health modifier already open.
And let's go through the settings.
Its attribute is max health modifier OP is override, but magnitude calculation type is attribute based.
That's not what we want anymore.
We now want to choose custom calculation class.
Now our options are different.
As soon as we expand custom magnitude, we can now choose a calculation class.
And if we expand the dropdown, the only option we have, aside from none, is our MSI called Max Health
MSI.
Max Health.
Now here's the crazy thing.
You can even sign coefficients and pre and post multiply additive values to this.
So even though this is a arbitrarily complex calculation that we created, we can even scale it by a
coefficient.
Whatever it returns, we can scale it, we can add a post, multiply additive value, a pre multiply.
It really is kind of crazy that we can do that.
Now remember, we captured the source and target tags.
We'll check this out.
We have dropdowns for those and we can choose, require and ignore tags require tags are tags that must
be present on the source in the case of the source tags.
In order to have this modifier applied, Ignore Tags says none of these tags may be present, so there's
the ability to say if the target has a specific tag, then we cannot apply this modifier.
So there's just yet even more options for you.
But now that we're using the custom calculation class, we should see something different for our max
health.
Let's just look at our calculation, Max.
Health should be 80 plus 2.5 times vigor plus ten times player level.
Let's see if the math works out there.
I'm going to go ahead and press play and right away, since we're running in debug mode, I got a problem
here.
Combat interface is null and that's because we called get source object and I'm assuming our source
object is null.
Let's make sure let's hover over spec or either that or coming down here and expanding the dropdown
for spec.
And our source object will be buried deep in effect context.
Let's expand its data and here's source object.
And we see that its null pointer, which makes sense because we're not setting a source object.
Where do we set the source object?
This is something you should know.
Remember, it goes back to where we're applying the effect and we're doing that in the character class.
If we open the private folder, open character and go to Aura character dot CP, scroll down.
Nope, it's actually Aura character base.
Yes.
Apply effect to self.
Here we're applying the effect and here is where we have the opportunity to set a source object.
Right?
So let's make sure we're setting a source object.
Let's go ahead and hit stop.
And how do we set a source object?
Can you remember if you can't remember, at least think back to where we did this before, right?
Where did we set a source object before?
If you thought about aura effect actor, you'd be right.
We can go back to apply effect to Target where we added a source object here.
We added it to the effect context handle.
So we have our gameplay effect context handle which has add source object.
So let's go back to Aura Character base.
We have an gameplay effect context handle.
Now here's the thing is we made it const, but now we need to change something on it so we're not going
to make it const anymore.
We're going to take context, handle and use add source object.
And for the source object, this is very important.
We have to add the character.
Right?
We have to add this.
Why?
Because the object itself, which will be aura in this case, or in the case of the enemy, the source
object is the class that has the implemented interface function get player level from our combat interface.
So that's why we need to use the character itself.
So now when we apply the effect to self, it'll have a valid source object.
So this was a pretty good learning exercise running in debug mode and capturing that problem, right?
If we were not running in debug mode, we would simply have crashed the engine and we might not be really
all that aware of what caused the crash.
But since we did it this way here in Maxhealth, we were able to see that our cast to combat interface
failed and we could dig deep into our gameplay effects spec to see that that source object was indeed
null.
So let's run in debug mode yet again and try this again.
And keeping in mind that calculation, 80 plus 2.5 vigor plus ten player level.
Let's make sure that that is checking out when we look at the value for max health.
Okay.
So first things first.
I'm going to go back into blueprints, ability system, gameplay effects, default attributes, GE or
a secondary.
The reason is because we kind of crashed.
I mean, we hit a breakpoint in debug mode and then closed the engine with that stop button.
So that means that any changes we made that we didn't save are not going to be here.
And as we can see, our max health modifier is back to being attribute based.
So we need to set its magnitude calculation type to custom calculation class and expand that dropdown
and set the class to max health.
And that way we can use this.
And you know, to avoid losing that progress, we can just save all and let's press play.
All right.
So we're so far, we're good.
Let's see if we can see our max health value.
We may not be able to because we just have too many attributes.
Yes.
So unfortunately.
No, wait.
I see it right here.
Here it is.
I don't know if you can see it on your screen.
It might depend, but I see 112 .51 12.50.
Let's see if 112 .50 makes sense.
We have 80 plus 2.5 times vigor.
And what was our vigor?
I can't see it in debug, but I can know by checking on the vigor modifier here and this will be a lot
easier once we make our attribute menu where we can see all these.
But let's find it.
It's actually our one of our primary attributes.
So we'll open that.
And here are our four modifiers.
Vigor starts off at nine.
Okay, so vigor starts at nine.
So we have 80 plus 2.5 times nine.
80 plus 2.5 times nine plus ten times player level.
Now, our player level should just be one.
We set a default value of one.
Now I'm going to just pop up a calculator and see if we can calculate this.
So I'm going to add that 80 at the end.
First, I'm going to do nine times 2.5, nine times 2.5, 22.5 plus ten times one.
So that's plus ten.
32.5 plus 80.
And that gives us one 12.5.
Is that what we had?
I believe it was.
Let's just double check one 12.5.
Yes, one 12.5.
So the math adds up.
It checks out.
This is correct.
And we also know that this is working properly.
In fact, we can place a breakpoint here and simply start the game.
That's going to trigger the breakpoint immediately because we apply this effect right away.
And we can see all sorts of information.
Right?
We see that player level is one as we expected.
We see that vigor is nine as again, we expected that.
And of course we can hover over properties here in debug mode and expand dropdowns to get all the information
at this point in time where the breakpoint was triggered right on this line.
So as you can see, the mod magnitude calculation is a very powerful way to have a custom calculation
type in one of our modifiers, and it does have its limitations.
It can only modify a single attribute.
So if we want to use more than one calculation, we'd have to add another modifier and modify the same
attribute again, but for what we needed, which was to access a non attribute value that player's level.
This works just fine.
So that's how we can have our max health dependent on both vigor and level which exists on the player
state.
However, if this effect was applied to say, an enemy whose level is not on the player's state but
instead on the character, it doesn't matter.
This mod magnitude calculation doesn't care about that.
It actually checks for the combat interface.
So really that enemy would just have to implement the combat interface.
And we're good, we're flexible, we're well coded.
So now that we know how to do this for max health, we can do it for Max Mana.
And this will be your quest.
So you're going to create a mod mag, calc yourself, create an MSI for Max Mana, and this will be
its own modifier magnitude calculation.
Now you're going to want Max Mana dependent on some attribute and the player level.
I'm using intelligence, so that means you're going to need to capture intelligence and you're going
to need to decide on a calculation for how to calculate Max Mana in this calculation.
So pause the video and conquer this quest now.
All right.
So we need to create an MC class.
So I'm going to go back to C plus plus classes or a public ability system.
And we have our mod mag calc folder.
I'm going to put it here.
So new C plus plus class choosing all classes and searching for mod mag not mag but mag.
We can use gameplay mod mag calculation or magnitude calculation.
Now I'm going to call this one MC underscore max mana.
I just like to prefix mine with MC so we know what we're looking at.
So let's create the class.
I'm going to close the editor and we're going to do something very similar to what we did for Max Health.
So let's go ahead and close out of some of these unused files and we'll open I'm going to actually close
private and public just so they open all neat and tidy like this.
And here's my MC Max Mana dot CP Control co brings open the header file as well, so I'm going to take
max Mana dot CP all the way to the left and max mana dot h right next to it and we have our health right
there for reference and we can just check.
And if this is what you did for the challenge, congratulations, because that is exactly what I expected
you to do, is to look at Max health and see how we did it here, because really that's what programming
is all about, doing whatever is necessary to make your job easier, and you're always going to be able
to look at other code, look at things you've done before.
It'll help you out.
So first we have a constructor here in Max Mana.
We're going to implement that.
We need a public section though.
So we have our constructor there.
We're going to generate the definition for that.
Perfect.
Now let's look at the next thing we want, which is this function we want to override.
We can go ahead and copy that and paste it on in.
Perfect.
Let's go ahead and generate the definition for that.
And remember, we don't want to call super here.
We just want to return our calculation.
And then next we had a private section with a gameplay effect attribute capture definition.
I'm going to go ahead and copy that whole thing and paste it on in right here.
The only difference is I don't want to capture vigor, so I'm not going to call this vigor def, I'm
going to call it int def for intelligence, and that's what I'm going to capture in max Mana dot CP.
Now back to Max Health.
We can see exactly how we captured this stuff.
We set the attribute to capture, we set the attribute source to Target, we set snapshot to false,
and then we added our capture definition to the array of capture definitions in the MK.
That's exactly what we're going to do.
So let's copy it and paste it.
And here's a nice trick we can do.
After hitting alt enter to include the aura attribute set, I'm just going to use control H to replace
all vigor here with.
What is it that I want to capture?
I want to capture intelligence.
Actually I can just do INT but this will be changed to get int attribute.
It has to be intelligence attribute.
That's fine, I'll fix that.
Let's click replace all.
And then I just have one thing to fix.
This has to be get intelligence attribute.
Other than that, that was pretty smooth.
So same thing.
Just int instead of vigor.
Now let's see what we did in the calculate base magnitude function.
We got source and target tags, we made evaluation parameters and then we called get captured attribute
magnitude, passing those in, creating a local vigor.
We're going to make a local int this time.
And then we got the player level using the source object, casting it to an interface.
So why don't we just let's just copy the whole function and we'll paste it in here and we'll just change
things.
The first like half of the function is fine, but we don't want vigor, so our local variable will be
called.
I'm just going to call it int and we're going to pass in int def instead of vigor def and we're going
to pass in int instead of vigor, we're setting vigor equal to the result of a max between int and zero.
Did I say vigor?
I'm meant int sorry, that was confusing.
Let me say it again.
We're setting our local int variable equal to the result of max.
And the result of that is that we're making sure that it's not negative.
If it is negative, then we will set it to zero and this keeps popping up.
It's really annoying.
Writer says, Hey dude, you're going to include the header anytime soon, so I'm going to hit alt enter
and choose to not forward declare but include the header for combat interface.
So once we've captured int successfully, then we can cast the source object to a combat interface.
We should never attempt to apply a gameplay effect that uses this calculation to anything that does
not implement a combat interface.
I'm going to consider that a mistake and for that reason I'm not going to check the pointer.
I'd rather it just crash instead or running in debug mode.
Just halt right here instead.
So I'm not going to check the pointer and we're getting the player level from it.
And now we choose a calculation here.
Now for Max Mana, I'd like a base value of 50 no matter what, and I'd like to also add every time
our intelligence goes up, I'd like to add some like at least like 2.5 F times our intelligence.
But as the level goes up, let's add even more.
Let's add, say ten, maybe even 15 point F times player level, something like that.
So pretty simple calculation.
You know, there's potential here to be much more complicated if we needed to.
But for my Max Mana, here's the calculation that I'd like.
And with that we now have an MSI.
We can go ahead and run.
I'm going to run in debug mode.
And with that we can open up our secondary attributes effect, scroll on down and our last one, index
nine is Max Mana.
It's attribute based.
We're going to switch that to custom calculation class and expand that and choose.
Now we have Max Mana.
We're going to choose that and that's it.
We can go and press play and I don't know if we can see Max Mana anymore.
We have so many attributes and so many infinite effects applied to them that we can't really see our
max mana.
At least I don't see it on my screen.
So what I could do is simply place a breakpoint there and run the game and then we can see that we at
least have the correct values.
INT is 17.
That's correct.
Our level is one.
So yeah, this function will return this value.
And once we add some more UI elements, I'm going to go ahead and resume.
Then we'll be able to display some more of our gas variables to the screen a lot more easily.
And so that'll be some of the next things we do.
But notice that we're starting off our health and mana pretty low and I don't want to have to hard code
those values, right?
So I'm going to save all.
If we go up to our attributes, set up to the constructor, we see that we're setting these both to
low values ten each.
I don't want to do that.
I want to set them equal to the value of max health.
But the value of max health depends on the value of other attributes.
So how are we going to get the health and mana initialized to the correct value, the exact value they
need to be to fill up those globes?
We'll figure out how to do that next.
I'll see you soon.