Welcome back.
Now, here in our Attributes set, we're checking to see if the changed attribute here in post gameplay
effect execute is incoming XP our meta attribute.
Now, if it is, we're adding to XP using the player interface that allows us to actually change our
XP on the player state.
But now that we're concerned with leveling up, we need to check to see if this is enough for a level
up.
If it is, we want to level up the character.
Now what happens as a result of that depends.
Yes, we're going to award certain values to various classes so that we can increase our level of progression,
but also we're going to most likely have some visual effects and in particular some effects related
to the player specifically.
So I'd like to add another player interface function for leveling up.
So I'm going to go into my public folder, into interaction and into player interface, and we're going
to add another function to this, and this one will be for leveling up.
Now it's going to be void and I'm just going to call it level up and we can implement this however we
like in our Aura character.
And I'm also going to make it a Blueprint Native event.
So we can override this in aura character however we like.
For now, I'm just going to override it and then we'll decide what happens as a result of calling this
later.
So we can say virtual void level up implementation override and we can generate an empty definition.
We don't need to call super for this.
We're going to do whatever we want here.
Most likely going to be cosmetic.
But now that we have this function, we can decide in our attributes set if we've just gained enough
XP to level up and that's going to involve some kind of calculation.
That's why we have our to do here.
So we'll do that.
I'd like to do that next in our next video.
But before we wrap up this one, I'd like to do one more thing.
I'd like to go into my interaction folder again into combat interface and notice that we have a lot
of Blueprint Native events and we've seen that this is really nice because it allows us to avoid the
cast to the interface and call a static function.
The execute version related to this, but get player level is not one of these and we're using get player
level all over the place and I really prefer that it be one of these blueprint native events so that
we can execute it without a cast just like the others.
That way in our aura attributes set where we're calling get player level, we can be consistent about
it here and send Exp event.
For example, we're casting to the combat interface just so we can call get player level.
If get player level were a blueprint native event, we wouldn't have to do that.
We could just check to see if the target character implements the interface and then call execute get
character class.
Now this isn't a problem.
This is a totally legit way to do it.
It's not bad practice or anything like that, but I would prefer if this were consistent here, if I
got both of these in a consistent manner.
That's one reason I'd like to change it.
But another reason I'd like to change it is because complications can occur when we're relying on something
to implement an interface.
And I'm going to show you one of those.
If we go into the private folder and ability system and open exec calc damage.
Here in exact calc damage all the way up at the top where casting the source, Avatar and target Avatar
both to these combat interfaces.
Now, that's fine if they both implement those and if they didn't, well, we could perform a null check
or something like that.
But why are we using the combat interface?
Well, if we search this file for interface, we can see that we're calling get player level here and
we're evaluating curves from curve tables based on the player level that we get.
Now, this is great.
Again, if both the damage causer and the receiver implement this combat interface, this works just
fine.
But if let's say we're getting damage from an effect.
Actor Right.
A fire area, well, that fire area may or may not implement this interface, at which point we'd be
trying to cast to the interface.
The interface would be a null pointer in this case and we'd be trying to access a null pointer again.
We could perform a null check, but it'd be nicer if we could just check to see if these actors implement
the interface and then have some local level variable that we can set to zero if they don't implement
the interface.
And then we would just have zero in our calculations.
So what I'd like to do is change the function in the combat interface, get player level so that it's
a blueprint native event.
And of course that's going to require us to go through the project everywhere it's used and change it.
We're going to have to change what we're overriding in both a character and or an enemy to the implementation
version.
And then everywhere we're calling the function, we have to call the static execute version instead.
So this will require a little bit of a project refactor, not a big one, but a little one.
So I'd like to do that in this video.
And this can happen in a project from time to time where you decide to change a design.
So the first thing we need to do is we're going to remove virtual from the function and we're going
to add a new function with Blueprint Native event.
That's the first thing.
Now as soon as we've done that, we're going to create lots of errors.
If we go ahead and compile right now, we'll get compiler errors.
So the first errors we get are in or character and or enemy like I just mentioned, get player level.
This is no longer something we override.
Instead we override the underscore implementation version of this.
So we have to do this both in the H file and we have to go into the CPP file and change it here as well.
So I just did for or a character we can do so for Ora Enemy as well, changing it from get player level
to get player level implementation, there's the header file and here is the CPP file.
So that takes care of those errors.
But if we compile again now we get other errors.
So how are we going to fix this?
Well, what I'm going to do is do a project wide search for get player level and I'm going to see everywhere
where we're using it.
The first place I see is the MSI, Max Mana, so I'm going to start there.
We're calling it right here After casting to the combat interface, instead of casting our spec, get
context, get source object to the combat interface.
What I'd rather do is check to see if it implements the interface and set player level.
And if it doesn't, player level can be set to say one.
So I'm going to do this.
I'm going to create a local non-const int 32 called player level and set it equal to one and remove
that line where I've declared that const int 32 and instead of casting to I combat interface, I'm going
to use an if statement.
And in the if statement I'm going to take this source object spec, get context dot get source object
and I'm going to paste it here.
And on this source object I'm going to call implements and implements allows me to check to see if it
implements the interface.
But very important.
We have to remember that if we call implements, we use the U version and not the I version.
So we use U combat interface for this implements function call.
Now, if we make it inside this, then that source object does implement the interface and then we can
set our player level by calling the static execute version on I combat interface.
So it can be a little confusing.
When we're using implements, we use U combat interface, but when we call that function on it, we
use I combat interface and the function is execute, underscore, get player level and when we call
the execute version.
Since it's static, it needs to know which object we're calling it on.
That's going to be this source object.
So we're going to pass that in as well.
And now we don't need this cast to eye combat interface.
We're going to go ahead and remove that.
And now we have just this line here.
So these lines where I create the player level and set them, we can use these instead in MK Maxhealth
as well.
So I'm going to copy them and go into my MKs folder into MK Maxhealth and replace these two lines here
where we're casting to the interface and creating this player level local variable.
I'm going to replace them with this function call where we check to see if the source object implements
the interface and then we call the execute version of get player level.
So that's going to take care of the usage of get player level in the MKs.
I'm going to go ahead and close out of those.
We're done with those and I'm going to search project wide for get player level again and see where
else we're calling it.
I see that I'm using it in the attribute set, but we'll get to that.
I'd like to skip to the exec calc as we're using get player level a couple times here when we're accessing
the player level to calculate our armor penetration coefficient and so on.
Now what I'd like to do is scroll up to the top because we're storing pointers to the combat interface.
What I'd rather do instead is just calculate the player level and have a couple local integers for player
level instead of just calling get player level.
In multiple instances here, the way we did it is totally fine, but I'd like to do it a little bit
differently.
So what I'm going to do is first check our source avatar and see if it implements the interface.
So we're going to say if.
Source Avatar.
Implements you combat interface.
And if it does, we can get the player level for it.
So I'm going to make a local int 32 called source player level.
I'm going to start it off at one without a dot f and now I'm realizing, I think I went into my mix
and I made an int 32 with dot f.
That's just habit.
I tend to put a dot f after a lot of things, so it's an n 32.
We don't need a dot f there that would result in an implicit conversion.
So I'm going to remove those from there.
A little tangent there, but I'm going to have this source player level and I'm going to take my source
player level here and set it equal to eye combat interface, execute get player level passing in source
Avatar.
So I'd rather do it this way.
And now we have just an int 32 for source player level.
I'm going to have the same thing for the target as well.
So we're going to have a target player level and this will be checking if the target Avatar implements
the interface.
And if it does, then we're going to set target player level equal to get player level passing in Target
Avatar.
Now that we have source player level and target player level, we can delete these pointers as we don't
really need them anymore and we can find where we're calling get player level here.
So instead of calling source combat interface, get player level here, we can just pass in source player
level.
And right here we're calling target Combat interface, get player level.
Instead.
I'm going to pass in target player level and I believe we're using it again here.
I'm going to replace this function call with target player level as well, and that is the only place
we use that interface.
So now we don't need to cast to the interface.
We're just checking to see if we implement the interface.
Okay.
So searching project wide again, I see that aura attribute set looks like just about the last place
we're calling get player level.
Actually almost.
We're calling it an aura ability system library as well.
So first the attribute set and send Exp event.
So here we don't have to cast to the combat interface.
Instead, we're going to take props dot target character and see if it implements the interface.
So we'll replace the conditional here with props.
Dot target character call implements.
You combat interface.
And if it does, then here we'll call that function I combat interface execute get player level passing
in props dot target character.
So now these two local variables are obtained in consistent manners and it looks a little bit more professional.
All right.
So the last place I think we're using this is in our Ability system library.
Let's go to that and that's here and give startup abilities where again, we're casting to the combat
interface, but instead I think we can just check.
So we're going to take our ASCs get Avatar actor function call.
We'll replace this conditional with get Avatar actor calling implements.
We'll check you combat interface.
And then instead of calling get player level on the combat interface directly, we'll call eye combat
interface, execute get player level and pass in ASC, get Avatar actor.
So that's going to replace our cast there.
Now I'm going to quickly just give this a compile and see what we get and we get a linker error.
Okay, So here's one that might have tripped you up if you tried to compile and do this.
Refactor yourself a linker error that says that a function is already defined.
This can be a little bit tricky, but it all comes back to this get player level function and how it
used to be.
Not a blueprint native event.
Now it is one.
So if we go to combat interface here, now we have get player level right and when we have this get
player level function as blueprint native event, we have to go into the CPP file here as we've defined
a function definition for it.
We can't do this because this get player level definition will be auto generated by the unreal header
tool.
So the unreal header tool sees that blueprint native event macro specifier and generates the implementation
for get player level.
For that reason, this get player level is a redefinition which is not okay.
So for that reason we have to remove this function call altogether.
If we do that, we should be able to compile just fine and we do.
Okay.
So that was a pretty good refactor.
We can do a quick play test and make sure that everything works okay.
Everything should still work the same as it did before.
We've just kind of cleaned up our code so it's a little bit more consistent.
So really quickly, I'm just going to press play caused some damage, make sure that things are working.
I can even take some damage.
And we see that things are working.
Everything works just fine.
We'll do a really quick multiplayer test as well.
Take some damage, caused some damage.
And things are working.
And I'll play his client as well just to try.
Dedicated server setup.
Perfect.
Okay.
So with that, we've just done a little bit of a refactor and we're ready to continue implementing level
up capabilities and we'll level up in the next video.
Excellent job and I'll see you soon.