Welcome back.
Now I've got my exec calc damage open here, and we're performing a couple of calculations that involve
some sort of magic numbers, some coefficients that scale these values, these attributes that we're
capturing so that they make sense for the calculation.
In other words, without them, the calculation may not be so natural or appropriate for RPG combat.
We have our effective armor, which we're calculating using target armor times.
This coefficient looks like I used times equals, which actually changes the target armor variable here
our local variable.
But that's okay.
This still works because the result of this highlighted expression is going to be the result of this
calculation.
So target armor times this amount, it actually does return that.
So this is the same as target armor times without the equals there.
But if that confused you, it's totally fine to just have the star without times equals.
Anyway.
We have a 0.25 coefficient for source armor penetration.
So for points of source armor penetration results in 1% of target armor ignored.
That's what this equation means.
But this coefficient may not make sense at all levels of combat.
This may make sense at the beginning of the game when all of the participants in combat have relatively
low stats.
But perhaps later on in the game when all participants in combat are level ten, for example, this
coefficient may need to change.
It may need to go down, for example.
So for that reason, these hard coded magic values are probably not the best way to do this in a serious
RPG game.
So for that reason, I'd like to explore a solution for this.
Now, we know that if we want something to change based on level, then a curve table is nice.
We can specify how those values change and I'd like to use a curve table for damage calculation coefficients
because this is a great opportunity to do so and we can have a single source that we can access those
from.
Now, where should we put those coefficients?
Well, I'd like character class info our data asset because it has our common class default values.
So we could easily put a scalable float here or just a curve table that we could access values from.
And that curve table could store curves for each of our coefficients that we need so far.
We just need to we need a coefficient to multiply source armor penetration by and we need a coefficient
for effective armor.
So I'd like to make a curve table for those.
So I'm going to browse to the folder that contains this.
This is ability system data and I'm going to make a curve table for our damage calculation coefficients.
So I'm going to right click, go to miscellaneous and choose curve table.
Oops, I selected data table.
Let's go back.
What I want is a curve table and for this we have an opportunity to check out the constant curve.
Interpolation type.
Constant is great because we only need a certain value from one level to the next level.
It doesn't need to smoothly change between levels.
Let's see how this works.
I'm going to select Constant, create a curve table and call this c t underscore damage calculation
coefficients.
And we'll open this up and we'll have a new curve.
I'm going to rename this default curve and we can call this first one armor penetration.
And these are all coefficient curves.
So this is really armor penetration coefficient, but rather than just sticking coefficient on the end
of all the names of the curves, I'll just name it armor penetration.
Now we need a key.
And notice the symbol here is not a triangle anymore but a square.
And if I click that, notice that it looks just like adding a key to a curve table.
It doesn't look any different than before.
But we'll see as soon as we add multiple curves and take a look at the curve view, how this differs.
Now at level one, I'd like to use the value that we're using for armor penetration 0.25.
So I'm going to stick that in here.
0.25.
And I'd like this to stay at 0.25 for a while, up to, say, level ten, for example.
Now, how can I do that?
Well, I can click on the square and click the three dots next to the two and select Retime keys.
At two, we can choose ten and at ten, I want to change this value now with 0.25, Let's think about
how the mathematics works.
Let's say source armor penetration is 100.
Well, this multiplies it by 0.25.
So that would make this multiplication 25 100 -25 is 75 divided by 100 is 0.75.
So we're taking off 25% for 100 source armor penetration points.
Now, I'd like this to be more harsh as we level up.
So at level ten, I want to multiply source armor penetration by an even smaller value.
So it takes even more armor penetration points because as these characters level up, source armor penetration
may get really, really high.
So I'd like to stick this to a lower value, say 0.15.
And in addition to that, I'd like to go up to level 20.
So I'm going to add another point.
It's set to 19, but I'm going to retime it at level 20 and at level 20.
I'd like this to go down even more like 0.85 or I should say 0.085 and we can just go ahead and go up
one more.
We'll go up to 40.
So let's retime this at 40 and at 40 we're going to make this pretty low, like 0.035, for example.
Okay, so what does a constant curve interpolation type look like in Graph View?
Well, let's go to Graph view and select armor penetration.
And here's what it looks like.
It looks like steps.
This is sometimes known as a step graph and it basically just means that between level one and ten,
we have one constant value 0.25.
So it doesn't matter what the level is.
If it's between 1 and 10, we'll always get 0.25 from that.
If we pass in 11, then we get the value here 0.15 and any value between 10 and 20 we get that value.
So it's a constant curve.
That's how it's going to work and that gives us a curve for armor penetration.
Let's add a curve for our effective armor.
That's the next coefficient.
We need effective armor.
So we're going to go ahead and click plus curve.
I'm going to rename the curve to Effective Armor, and we have the same time scale values in our case
level scale values, and we're going to start off at 0.333.
And this value can get lower as we move on.
So at level ten, we're going to make this 0.25.
At level 20, we're going to make it 0.15 and at level 40 we're going to make it 0.85.
And if we look at the graph, this is what it looks like.
Oh, I should have made this .085.
Right?
So 0.085.
So it's actually getting smaller.
So we don't have that big jump there.
And here's what the step curve looks like.
Pretty nice.
Okay, cool.
So we have two curves.
We can add this curve table to our character class info and we're going to do that in C plus plus.
So I'm going to save all and close and open my data asset.
So in ability system, we're going to go to the public folder actually ability system data, character
class info.
We're going to add a pointer to a curve table.
So let's add a T object pointer.
You curve table and we'll call this damage calculation coefficients.
And this can be you property Edit defaults only.
Common class defaults.
We could even put this in a subcategory of damage and we can set this in the editor.
So let's go ahead and launch.
And back in the editor.
Let's open up that data asset and here's our damage subcategory and damage calculation coefficients
is here and we can get that damage calculation coefficients curve table and set it there.
So now if we have access to the data asset, we have access to those curve values and we can get those
in our calculations.
So closing and saving again and we're done with character class info.
I'm going to close that.
We're back into exit calc damage, and I'd like to access those values here.
So how are we going to do that?
Well, we know that that data asset exists on the game mode, and if we can access the game mode, we
can access the data asset.
Now, I don't want to have to get the game mode every time I need some kind of value on that data asset.
It'd be nice if I had just some easy way to get that data asset.
So my ability system blueprint function library comes to mind.
I'm going to open up my aura ability system library and make a static function that can return that
data asset.
This will make things easy for me, so I'm going to do a static function that returns a you character
class info pointer and scrolling up.
I see that the header file for character class info is already included, so no need to forward declare
and I'm going to call this get character class info.
Now this will require a world context object, so I'm going to add that as a required input and we'll
go ahead and give this a new function macro blueprint callable or a ability system, library slash character
class defaults and we can define the function.
And this is going to be quite simple, actually.
We're going to get the game mode just like we're doing in give startup abilities.
And from that game mode we're going to access character class info and we can simply return it.
So I'm going to copy these lines and paste them down here and simply return our game mode Character
class info.
So we'll have a return statement there.
And that's all we need to do.
Now this return statement here has to return something, so we'll return null pointer in the case that
the game mode is null.
So if we call this on a client where the game mode is a null pointer, well we'll get a null pointer
then.
So get character class info is handy.
In fact, we can replace some lines of code and give startup abilities if we want to just use get character
class info instead of going through the same process.
So right here where we're saying character class info, we can just call get character class info passing
in world context object.
And then we don't have to get the game mode right there.
We can do the same thing here in initialize default attributes where we're getting the character class
info.
We don't have to get the game mode anymore so we can remove those lines and replace this line here with
get character class info passing in world context Object.
And we have the character class info and of course Avatar actor could be const class default info could
be const.
But for now, let's stick to what we're doing and that is to get character class info here.
This makes it easy to access character class info.
Now character class info has the curve table.
Let's go back and look at it.
It's called damage calculation coefficients.
We can access that if we can access character class info.
So closing that, I'm going to go back to exec calc damage and see if we can access character class
info and then we can get that curve table and get our coefficients that we need.
So right here I can say you character class info.
That's a pointer called character class info.
And I can go ahead and alt enter to include the header file for it and I can use you ora ability system
library or just added the include for that.
And we can use get character class info and we can pass in a world context object and we have access
to a couple of them.
We can pass in source Avatar for example.
So I'm going to go ahead and use the source avatar for the world context object there.
Once I have character class info, then I can access the data table from it, which is called damage
calculation coefficients.
So taking character class info.
We can get damage calculation coefficients and we can get values from the two curves.
So how do we get values from a curve table?
Well, we could take this curve table, use the arrow operator to see what kind of.
Functions we have now.
One of them is fine curve.
And if we find a curve, we have to specify the row name for the curve.
Now we named these after our attributes that the coefficients are supposed to affect, so we have to
pass in an F name specifying the name of that curve.
For example, we have the armor penetration.
We can use armor penetration.
This requires a context string.
We can just pass in an empty F string and warn if not found is true by default.
So once we find the curve, we can evaluate the curve at a given level.
So this find curve returns an F real curve and that's a pointer.
So we have F real curve.
And we'll call this armor penetration curve.
That's a real curve pointer.
And we can take armor penetration curve.
And from this curve we can evaluate.
So eval is just a way to get a value on the curve at a given input for a given level.
So if we call eval, we have to pass in a level, and for that reason we're going to need the level
of our source character, right?
We need a way to know what level that character is.
So how can we get the level?
Well, we have an interface, don't we?
We have our interaction folder.
Here's our combat interface, which has the function get player level.
So if we can get the combat interface for both our source and target, we can know what their levels
are.
I'd like to get those way up here at the top where we're getting our source and target avatars.
So we'll just store an eye combat interface pointer for each of these.
First, I'll make a source combat interface and I'll just cast to it Eye combat interface cast Source,
Avatar.
Now, here's the thing.
Source Combat interface has to be const because source avatar is const.
But that's a problem because we want to call get player level.
That's not a const function.
So there's an issue here.
If we make source combat interface const, we get rid of the error there, but then we can't call source
combat interface get player level.
Because that's not a const function.
As you can see, the error says the function is missing the const qualifier.
So in order to call get player level source, combat interface can't be const, which means source avatar
can't be const.
So we're not going to make this const anymore and Target Avatar will no longer be const and then we'll
have to non-const eye combat interface pointers.
We'll make this other one for target combat interface, which is going to cast Target Avatar.
So now that we have these two interfaces, we can get the player level.
So back down here in our eval call, we're going to get the source combat interface and call get player
level and pass that in.
Now Eval is going to give us the armor penetration coefficient.
This can be a const float called armor penetration coefficient.
And now that we're evaluating armor penetration coefficient at the source character's level, we can
use that in our calculation here instead of the magic number 0.25 and we can do the same thing for our
effective armor coefficient.
So down here, just before we do that calculation, we can do that too.
So we're going to say f real curve.
Pointer effective armor curve equals.
Character Class info.
Damage Calculation Coefficients.
Find curve.
F name and the curve Rho is called effective armor.
The context string is going to be just an empty string.
And once we have that curve, we'll make a const float called Effective Armor Coefficient.
And set that equal to effective armor curve eval.
Passing in and this is the effective armor of the target.
So this has to be target combat interface, get player level and we can take effective armor coefficient
and use that instead of the magic value.
0.333.
Now these curves can be const pointers, so we'll make these f real curves both const and character
class info can also be const, so we'll make that const as well.
So now we can simply test this out.
Now we know what those values should be at level one.
And so far all of our characters and enemies are level one.
So we just need to test this out and make sure that we get those values that we set in the curve tables
curves.
So let's test this out and we'll just use breakpoints to check those values.
And we'll see if we have any problematic situations that cause crashes.
Let's go ahead and press play.
And I forgot to add breakpoints.
So let's go ahead and add a breakpoint just at the end of the execution calculation and we'll press
play and cause some damage.
There we go.
And no crashes.
We got our break point and we can check these values.
Let's take a look at armor penetration coefficient.
I'm expecting 0.25, giving it a second to evaluate it.
And there it is 0.25.
And for effective armor coefficient I'm expecting 0.333 and there it is Now yes, there's a bunch of
zeros and a four at the end.
In your case it might be a different value that is due to floating point precision or as I like to call
it, floating point imprecision, because it's imprecise.
It's not going to be precise to this many digits, which is totally fine.
This many digits out is a very, very small level of inaccuracy.
But this is a good reminder that floating point values are rarely equal to each other, which is why
I almost always use a less than or equal or a greater than or equal when comparing two floats.
So that's just kind of a side tangent there.
But I'm seeing that these values are correct.
And another really good way to test this out is to change the level of the thing that we're hitting.
For example, this here is a BP goblin, Spear two.
It has its own level.
It's set to level one, but I'm going to set it to level 11.
Why?
Because that's when it's effective.
Armor coefficient should go up.
So now I'm going to go ahead and hit it with a fireball and take a look at the effective armor coefficient.
And it was 0.333.
Now it's 0.25.
Armor penetration coefficient is also 0.25.
Excellent.
And we know that it should have an even different value if we set that level to, oh, let's say 40
or 39, I'm going to set it to 39 and damage it.
And now the effective armor coefficient is 0.15 and just double checking in our damage calculation curve
table.
Effective armor at level 39 should be 0.15 and as soon as we hit 40 it'll go to 0.085.
And why not?
Why don't we just test out level 40?
We should see 0.085 and we see 0.085 with some floating point in precision.
Excellent.
This is great.
Now, our magic numbers have been replaced by data driven values, and now they can change based on
level.
And that gives us a way to have even more control As the player progresses gets higher in level, the
enemies get higher in level, and as things start to get out of whack a little bit, we can scale these
values according to our curves and have a little bit more precise control over how the mechanics work.
And what values should these be?
Well, that's going to depend on the game and is going to require a lot of playtesting.
But this is great because now as we add more calculations, we have access to that curve table and we
can scale things based on player level.
So things are looking great.
Now, if you're wondering why I'm creating all these local pointers and not checking to see if they're
a null pointer, that's because I don't expect to run this function in any situation where these pointers
should be null.
I'm considering it an error or a mistake if those pointers become null.
So accessing those pointers when they're null is going to crash the program and the crash will point
to here, which means that something is wrong and needs to be fixed.
So rather than having it fail silently and not crash the program, I'd rather just leave it here and
crash the program.
And if I wanted the crash log to be more descriptive, I could use assertions here and check these values
and put a specific message to the output log in the case that these are null and attempted to be accessed.
But in this case, if the game crashes and points to one of these lines here, I'm going to know why
and address it then.
But as we saw, the game should not crash because these curves are going to be valid because we've set
the curve table in the data asset.
So really the only reason it would be null is if we had forgotten to do that, Right?
So this is looking great.
Combat's looking good.
I'm going to set this character back down to level one and we can continue with our damage execution
calculation and we'll do that in the videos to come.
I'll see you soon.