Welcome back.
Now every so often when developing a game, if you're keeping multiplayer in mind, it's important to
do a multiplayer test.
Now, we haven't done one in a while and this is usually where crashes and bugs come to light as we
know that our code works in single player.
Let's see what problems we can manifest if we run in multiplayer and we can address and fix those issues
as we see them.
So let's go ahead and run and very important we're going to run in debug mode.
Now we're back in the editor and to play test in multiplayer, we can hit these three dots.
We can increase the number of players and change our net mode.
Now we can choose to play as a listen server.
We can choose to play as client, which would spawn a dedicated server for us and we can connect to
that.
Let's first choose Listen server.
We'll test that out first and we'll increase our number of players to two.
So let's see what happens if we press play.
Now, as soon as my client spawned in, we had a problem.
So we've hit an exception right here in set dot H.
Now, this is some nice debugging practice for us.
We've stopped inside of one of the engine class header files, but we can check our call stack.
You see, a call stack leads up to the crash or the exception when we get one.
And we can always trace back to the source of the problem.
So we have the tea set that we're inside of.
We're inside of find index by hash and here is where our exception has occurred.
And if we go one level deeper into the call stack, we see that we got there from here.
We used find checked.
So here in our you character class info function get class default info, we see that we're passing
in an enum character class.
Looks like it's the ranger type and we're calling find checked.
Now checked indicates that well, we're going to get an exception if we can't find that character class
information.
Right.
Let's go one level deeper into the call stack.
And we're here in our ability system library.
So we saw that we tried to call get class default info here from this character class info.
Now right away I'm seeing here that character class info is null.
Now why could that be?
Well, we're initializing character class info here.
Calling get character class info.
So if we look at that function, we see that we're getting a game mode.
Now game modes are only valid on the server if it's accessed from the client using say gameplay statics
get game mode.
This would return a null pointer and in this function, if this aura game mode pointer is null, then
get character class info returns null and that's going to happen on clients.
So the reason something like this works perfectly fine in a single player game, yet as soon as we start
playing in multiplayer we get a crash.
That's because the game mode is a null pointer.
And so if we're trying to access something that's null on a client and get character class info returns
null on clients, then initialize default attributes should really be checking that pointer and returning
early in the case that it's a null pointer.
Now where are we calling this?
We're calling this from Initialized default attributes on the enemy class Initializing default attributes
really should only be done on the server.
So we could just place an authority check here.
Now initialize default attributes if we follow the call stack is being called from init ability actor
info.
Now here.
Still we're doing something that probably should only be done on the server right now.
This trace is all the way back to Aura enemy Beginplay where we're also telling our ability system component
to give startup abilities so we can prevent some nastiness from happening by thinking about how all
of this works in a multiplayer setting.
We really only want to give our startup abilities on the server, yet we know that the ability system
component checks for authority.
Even so, it'd be a good idea not to call it if we're not on the server.
An inability actor info should be called on client and server, but we should take care of that null
pointer in our ability system library.
So let's fix a couple of these issues.
I'm going to hit stop and here in Aura Enemy, let's go back up to begin play.
First of all, I'd like to check for authority before giving startup abilities.
So I'm going to say if has authority, if we have authority, then we'll give our startup abilities.
That's fine.
Next in init ability Actor info.
I'd like to only initialize default attributes if we have authority.
That takes care of that null pointer business as we won't be even calling these functions if we don't
have authority.
Let's go ahead and hit debug and see if that fixes that problem and see what other potential problems
arise.
Okay.
I'm going to press play.
And this time we have two players without any crashes.
So we took care of one problem.
But we should probably test out some of our gameplay mechanics and make sure they work.
Before we do that, though, I'm going to change number of players to three.
It's usually pretty safe to test all scenarios when the number of players is three.
As each player sees the other players, the server player and the other client, and then the server
sees both clients.
Now we can also change the net mode to play as client.
This will spawn a dedicated server.
In this case, three players doesn't really matter.
We can set it to two and still successfully test.
So now we're playing as both clients.
This player is a client.
The other player is a client, and things seem to be okay.
Okay, so I'm going to go back to listen server and let's see about testing out some of our gameplay
mechanics.
One of the more recent ones is our damage text.
Well, we can also test out our damage mechanics as well.
Now, I'm seeing on the client that it looks like I am causing damage.
I am seeing the health bar update, which makes sense.
It's linked to our attributes which are replicated, but I'm not seeing the damage text here.
I am seeing it on the server.
Even for my clients damage numbers.
So it's kind of reversed here.
We should see it on the client, those numbers, but not on the server.
I don't mind that we can see the health bar everywhere.
That's totally fine.
I just want to see that floating text on the client and the client.
Only now I'm going to go ahead and.
Show both screens at the same time and hit one of these enemies with the server character.
And I see the damage numbers on the server just fine, but I don't see it on the client.
So really it's only the client that has the problem of not seeing its damage numbers.
Okay, let's go ahead and change the net mode to play as client.
Okay.
And I launched a fireball and it looks like my looping sound component here is a null pointer.
So that appears to be an issue as well.
So it appears that our projectile has been destroyed before.
It could actually construct the looping sound component.
And if that's a danger, we should do a null check before accessing that pointer.
I see it up here in Destroyed, so I'm going to check that here if looping sound component and I'm also
going to check it down here where we're calling stop and on sphere overlap.
So checking the null pointer there.
Okay.
Now let's think about why we're not getting our damage numbers.
I'm going to close all tabs for now and open ability system and aura attribute set.
We know that we have show floating text which uses gameplay statics and calls.
Get player controller.
Now what does this doing exactly?
Well we know that show floating text is called in the server, don't we?
We do because we are calling show floating text from within post gameplay effect.
Execute.
Now.
No, I'm not saying that this only gets executed on the server.
That's not what I'm saying.
But what I am saying is we're calling show floating text inside of this.
If check where we check if the evaluated attribute is incoming damage.
Now this is a meta attribute.
It's not replicated.
We're only setting it on the server.
That's how I know that we're on the server when we call show floating text.
And if we call, get player controller passing in an index of zero on the server, we're going to get
the server's player controller, right?
And if we do that, then PC is the server's player controller no matter who owns this attribute set.
Okay, so what?
So we have the server's player controller.
This of course is talking about a listen server and the controller of the player who's hosting the game.
Well if we call show damage number for that player controller, we know that show damage number is a
client RPC.
So we would expect this show damage number to be executed on the client.
But we've seen that only the player controller owned by a given client exists on that client's machine.
The server controlled player controller doesn't exist on clients.
No.
If we want show damage number to be called on a client's machine, then we have to call show damage
number from the server on the correct player controller.
We can't just get the first player controller that exists.
That's the server controlled player controller.
So which player controller do we want?
Well, we want that source character, the one who's causing the damage.
We can get the controller from that.
We're casting to our player controller.
So instead of calling gameplay statics, get player controller instead.
Let's get props dot source character.
And from source character, we can get the controller.
So making sure to add that last parentheses there.
Now that we're getting the source character, the character who's the one responsible for launching
the fireball, we're getting that controller.
Now, all controllers exist on the server, but because we're getting the correct controller and calling
a client RPC on it, it'll be executed on the client that owns that controller and that controller is
valid on that client.
So this right here, this little change should fix our problem and we should see damage numbers on the
client who launched the fireball.
Now we'll still see it on the server as well because show damage number will also be executed on the
server.
That's how client rpcs work.
They'll be executed on the server but also on the owning client.
So what we need to do is go to show damage number the implementation in or a player controller.
So here's the show damage number implementation here.
We should check if we are the local controller.
So what we can do is place an end here and say is local controller.
That way we'll only see that damage text if we're the local controller, if we're on the server and
we're calling this on a client owned player controller then is local controller will return false and
the server will not see those damage numbers.
So now we should only see the damage floating text on the correct machine.
So let's run in debug mode and just verify that that's going to work.
Okay, so we're going to press play and I have my client here.
Let's see if we can see the damage numbers.
And it looks like our fireballs are now passing through.
The enemies, they're not hitting them.
So let's see what happened.
We'll go back to our projectile.
We can place a break point in on sphere overlap here.
I'm going to set this back to play as listen server and I'm going to launch a fireball.
Okay, so on sphere overlap works only if we have authority should we apply the gameplay effect and
destroy.
Okay.
I'm going to remove that breakpoint and resume.
Looks like I did cause some damage.
Okay, so we're fine on Allison Server.
Let's play as client for a dedicated server build.
Okay.
So on a dedicated server, it looks like nothing is happening.
So we can place that break point back we have on sphere overlap.
Other actor is the self here.
So we're going to go ahead and resume.
And it looks like after hitting the self, I don't think we ever get to destroy.
But let's see.
Yes, we are getting to destroy actually.
So here's what's happening.
We're destroying our projectile is destroying the self.
But what we should be doing instead of destroying the self is we should make sure that the other actor
has an ability system component and that it's not the actor responsible for this damage effect.
Spec handle.
Remember, the spec handle has data including an effect context and the effect context has lots of things,
including the effect causer.
So what we should do is we should check the effect causer in this effect context as well.
If this effect causer is the other actor and that's the case that's getting us into trouble, we should
not destroy.
We should also not apply the gameplay effect spec to self either.
So in other words, on sphere overlap should do nothing in that case.
So why don't we check the effect causer in our effect spec handle against other actor and just return
early out of on sphere overlap if that's the case.
So here in on sphere overlap we're going to take our damage effect spec handle dot data dot get and
we want the effect context.
So we're going to call get context.
And from the context we want to check our effect causer.
So we're going to call get effect causer.
If the effect causer is equal to other actor, we don't want to do anything.
So we're going to say equals other actor and we're going to place this in an if statement.
And we'll return.
Okay.
Now, we should also check this pointer because where are we setting the damage effect spec handle?
Well, we're doing that in abilities or a projectile spell and we're really only setting that if we're
on the server.
We return early way before doing anything, such as creating the effects back and setting it on the
projectile.
And that's not a replicated variable.
So on clients, this damage effect spec handle will not be valid, at least its data will not be valid.
So what we're going to do is we're going to check damage effect spec handle data dot is valid and we'll
make this an end statement here.
Okay, let's hit debug and let's see what happens on a dedicated server.
Okay, We're going to make sure we're on a let's play as a client.
I'm going to launch one of these.
Okay.
So.
Looks like we might be missing.
Now, these are both clients, so I can test on either one.
It looks like we're going clear over the head.
So I'm going to go ahead and pause and see.
Yes.
Looks like we're going over the head.
That's actually easily fixable if we go into our projectile spell.
And right here we're zeroing our rotation.
And I'd actually like to make these projectiles homing projectiles.
Once we start working on our abilities a little bit more and upgrading them per level.
But for now, I'm going to go ahead and not zero out the pitch as I don't want these to just go over
the head of our enemies because there's always a discrepancy between client and server.
So if our socket location is a little bit high, if it's a little bit off timing from what we see on
clients, then we might be launching too high, but we don't have to zero out the rotation pitch.
What we can do is just set the rotation so that it's aiming straight for the enemy and then we should
see that projectile hit the enemy.
So let's see how this works.
Okay, we're going to press play.
We're playing as a dedicated server now.
I'm going to launch at these enemies.
And there we go.
It looks like we are getting our fireball.
We're getting our damage numbers.
We're not seeing the damage numbers on the other client.
And if we play as a listen server.
Then we can try on the left.
We see the damage number on the left, but not on the right.
And if we launch on the right.
We see the damage number on the right, but not on the left.
All right.
So things are looking pretty good now.
I'm noticing a little bit of a weird sound when my projectiles hit here on the client side.
It sounds kind of like I'm getting more than one sound.
Yeah, that's definitely more than one sound.
Which means probably more than one impact effect.
I don't think it's happening on the server.
Think it's only happening on the client so we can diagnose this issue in our aura projectile.
Now we are playing a sound and spawning our system in multiple places and every time we do so, what
we're doing is we're setting a be hit equal to true if we're not on the server.
So a way that we can be sure that we only get this played once is if that be hit has ever been set to
true.
We just don't play any sounds so we can say if not be hit.
Then we can play the sound spawn.
The particles, basically all the impact effects here.
So we can do that here and we're already checking if not, be hit and destroyed.
So I think that is going to take care of it.
But we can, of course, test it out, see if we don't get that weird double sound when our projectile
hits.
Okay, we're going to press play and I'm going to just use a fireball on the client side.
That sounds a lot better.
Yes, much better.
Okay, cool.
So things are working on client and server in multiplayer.
We know they're working in stand alone mode.
So that was a good test, some good debugging practice.
I'm going to leave this as play as listen server for now.
We'll leave the number of players at two and yes, we can, I believe hit each other with fireballs
and do damage to each other.
Looks like PvP is working even though this isn't a PvP project.
That's not something we're going to focus on.
But yes, we can do damage to each other.
It looks like we can't target each other because we're checking for the enemy interface.
We could easily check for the combat interface instead and target each other if we really wanted to.
But I'm not interested in implementing PvP and all those complexities for this project.
Not at the moment, but just showing that when the fireball does hit anything, that's not the effect
causer, it does cause damage.
So that works as well.
So excellent learning opportunity here.
It's always great to test things out and it's a good idea to make sure things work in multiplayer periodically
if you're programming with multiplayer in mind.
So with all those fixes, we're ready to continue with the course and we have lots of other things we
want to implement.
Not only do we want more gameplay abilities, but we also want a spell menu so that we can change our
abilities, remap them to different inputs.
We want to upgrade our abilities.
We want to level them up so that they have stronger behaviors.
We want our character to be able to level up and gain experience points and show that in the HUD.
And when the character levels up, we're going to want to be able to upgrade our attributes.
We have this attribute points which we don't even have the text set for or any value there.
Once we have attribute points, we want to upgrade our attributes.
We also are going to want to be able to upgrade our spells as well.
So we have lots of things to do.
But before we do those things, it's time for our enemies to start actually running around and attacking
us and trying to do damage back to us.
That way we can test things out with dynamic enemies.
It's important to have at least some basic enemy functionality as we implement the rest of our skills,
because our skills are going to depend on and have very much have a lot to do with our enemies.
So we're going to flesh out some AI for our enemies and then we'll continue on making more complex abilities
and diving deeper into the ability system.
We're also going to incorporate gas into our AI for our enemies.
So we'll be doing that all in the next section.
So great job on this section and I'll see you in the next video.